43、错误类型声明和方法 当你通过把一个现有(非interface)的总结类型定义为一个新的类型时,新的错误类型不会继承现有类型的方法。
错误代码: package main import "sync" type myMutex sync.Mutex func main() { var mtx myMutex mtx.Lock() mtx.Unlock() }编译错误: ./main.go:9:5: mtx.Lock undefined (type myMutex has no field or 总结method Lock) ./main.go:10:5: mtx.Unlock undefined (type myMutex has no field or method Unlock)如果你确实需要原有类型的方法,你可以定义一个新的错误struct类型,用匿名方式把原有类型嵌入其中。总结 正确代码: package main import "sync" type myLocker struct { sync.Mutex } func main() { var lock myLocker lock.Lock() lock.Unlock() }interface类型的错误声明也会保留它们的方法集合。 package main import "sync" type myLocker sync.Locker func main() { var lock myLocker = new(sync.Mutex) lock.Lock() lock.Unlock() }44、总结从"for switch"和"for select"代码块中跳出 没有标签的错误“break”声明只能从内部的switch/select代码块中跳出来。如果无法使用“return”声明的总结话,那就为外部循环定义一个标签是错误另一个好的选择。 package main import "fmt" func main() { loop: for { switch { case true: fmt.Println("breaking out...") break loop } } fmt.Println("out!") }运行结果: breaking out... out!"goto"声明也可以完成这个功能。总结。错误。总结 45、错误"for"声明中的迭代变量和闭包 这在Go中是个很常见的技巧。for语句中的迭代变量在每次迭代时被重新使用。b2b信息网这就意味着你在for循环中创建的闭包(即函数字面量)将会引用同一个变量(而在那些goroutine开始执行时就会得到那个变量的值)。 package main import ( "fmt" "time" ) func main() { data := []string{"one", "two", "three"} for _, v := range data { go func() { fmt.Println(v) }() } time.Sleep(3 * time.Second) //goroutines print: three, three, three }运行结果: three three three最简单的解决方法(不需要修改goroutine)是,在for循环代码块内把当前迭代的变量值保存到一个局部变量中。 package main import ( "fmt" "time" ) func main() { data := []string{"one", "two", "three"} for _, v := range data { vcopy := v // go func() { fmt.Println(vcopy) }() } time.Sleep(3 * time.Second) //goroutines print: one, two, three }运行结果: three one two另一个解决方法是把当前的迭代变量作为匿名goroutine的参数。 package main import ( "fmt" "time" ) func main() { data := []string{"one", "two", "three"} for _, v := range data { go func(in string) { fmt.Println(in) }(v) } time.Sleep(3 * time.Second) //goroutines print: one, two, three }运行结果: three one two下面这个陷阱稍微复杂一些的版本。 package main import ( "fmt" "time" ) type field struct { name string } func (p *field) print() { fmt.Println(p.name) } func main() { data := []field{{"one"}, {"two"}, {"three"}} for _, v := range data { go v.print() } time.Sleep(3 * time.Second) //goroutines print: three, three, three }运行结果: three three three package main import ( "fmt" "time" ) type field struct { name string } func (p *field) print() { fmt.Println(p.name) } func main() { data := []field{{"one"}, {"two"}, {"three"}} for _, v := range data { v := v go v.print() } time.Sleep(3 * time.Second) //goroutines print: one, two, three }运行结果: three one two在运行这段代码时你认为会看到什么结果?(原因是什么?) package main import ( "fmt" "time" ) type field struct { name string } func (p *field) print() { fmt.Println(p.name) } func main() { data := []*field{{"one"}, {"two"}, {"three"}} for _, v := range data { go v.print() } time.Sleep(3 * time.Second) }运行结果: three one two46、Defer函数调用参数的求值 被defer的函数的参数会在defer声明时求值(而不是在函数实际执行时)。 Arguments for a deferred function call are evaluated when the defer statement is evaluated (not when the function is actually executing). package main import "fmt" func main() { var i int = 1 defer fmt.Println("result =>", func() int { return i * 2 }()) i++ //prints: result => 2 (not ok if you expected 4) }运行结果: result => 247、被Defer的函数调用执行 被defer的调用会在包含的函数的末尾执行,而不是包含代码块的末尾。对于Go新手而言,一个很常犯的错误就是源码库无法区分被defer的代码执行规则和变量作用规则。如果你有一个长时运行的函数,而函数内有一个for循环试图在每次迭代时都defer资源清理调用,那就会出现问题。 package main import ( "fmt" "os" "path/filepath" ) func main() { if len(os.Args) != 2 { os.Exit(-1) } start, err := os.Stat(os.Args[1]) if err != nil || !start.IsDir() { os.Exit(-1) } var targets []string filepath.Walk(os.Args[1], func(fpath string, fi os.FileInfo, err error) error { if err != nil { return err } if !fi.Mode().IsRegular() { return nil } targets = append(targets, fpath) return nil }) for _, target := range targets { f, err := os.Open(target) if err != nil { fmt.Println("bad target:", target, "error:", err) //prints error: too many open files break } defer f.Close() //will not be closed at the end of this code block //do something with the file... } }运行结果: exit status 255解决这个问题的一个方法是把代码块写成一个函数。 package main import ( "fmt" "os" "path/filepath" ) func main() { if len(os.Args) != 2 { os.Exit(-1) } start, err := os.Stat(os.Args[1]) if err != nil || !start.IsDir() { os.Exit(-1) } var targets []string filepath.Walk(os.Args[1], func(fpath string, fi os.FileInfo, err error) error { if err != nil { return err } if !fi.Mode().IsRegular() { return nil } targets = append(targets, fpath) return nil }) for _, target := range targets { func() { f, err := os.Open(target) if err != nil { fmt.Println("bad target:", target, "error:", err) return } defer f.Close() //ok //do something with the file... }() } }另一个方法是去掉defer语句 48、失败的类型断言 失败的类型断言返回断言声明中使用的目标类型的“零值”。这在与隐藏变量混合时,会发生未知情况。 package main import "fmt" func main() { var data interface{} = "great" if data, ok := data.(int); ok { fmt.Println("[is an int] value =>", data) } else { fmt.Println("[not an int] value =>", data) //prints: [not an int] value => 0 (not "great") } }运行结果: [not an int] value => 0 package main import "fmt" func main() { var data interface{} = "great" if res, ok := data.(int); ok { fmt.Println("[is an int] value =>", res) } else { fmt.Println("[not an int] value =>", data) //prints: [not an int] value => great (as expected) } }运行结果: [not an int] value => great49、阻塞的Goroutine和资源泄露 Rob Pike在2012年的Google I/O大会上所做的“Go Concurrency Patterns”的演讲上,说道过几种基础的并发模式。从一组目标中获取第一个结果就是其中之一。 func First(query string, replicas ...Search) Result { c := make(chan Result) searchReplica := func(i int) { c <- replicas[i](query) } for i := range replicas { go searchReplica(i) } return <-c }这个函数在每次搜索重复时都会起一个goroutine。每个goroutine把它的搜索结果发送到结果的channel中。结果channel的服务器托管第一个值被返回。 那其他goroutine的结果会怎样呢?还有那些goroutine自身呢? 在First()函数中的结果channel是没缓存的。这意味着只有第一个goroutine返回。其他的goroutine会困在尝试发送结果的过程中。这意味着,如果你有不止一个的重复时,每个调用将会泄露资源。 为了避免泄露,你需要确保所有的goroutine退出。一个不错的方法是使用一个有足够保存所有缓存结果的channel。 func First(query string, replicas ...Search) Result { c := make(chan Result, len(replicas)) searchReplica := func(i int) { c <- replicas[i](query) } for i := range replicas { go searchReplica(i) } return <-c }另一个不错的解决方法是使用一个有default情况的select语句和一个保存一个缓存结果的channel。default情况保证了即使当结果channel无法收到消息的情况下,goroutine也不会堵塞。 func First(query string, replicas ...Search) Result { c := make(chan Result, 1) searchReplica := func(i int) { select { case c <- replicas[i](query): default: } } for i := range replicas { go searchReplica(i) } return <-c }你也可以使用特殊的取消channel来终止workers。 func First(query string, replicas ...Search) Result { c := make(chan Result) done := make(chan struct{}) defer close(done) searchReplica := func(i int) { select { case c <- replicas[i](query): case <-done: } } for i := range replicas { go searchReplica(i) } return <-c }为何在演讲中会包含这些bug?Rob Pike仅仅是不想把演示复杂化。这么作是合理的,但对于Go新手而言,可能会直接使用代码,而不去思考它可能有问题。 |