Go在语言层面通过Goroutine与channel来支持并发编程,使并发编程看似变得异常简单,但通过最近一段时间的编码,越来越觉得简单的东西,很容易会被滥用。Java的标准库也让多线程编程变得简单,但想当初在公司定位Java的问题,发现很多的同学由于没有深入了解Java Thread的机制,Thread直接New从不管理复用,那Goroutine肯定也要面临这类的问题。
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}在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新手而言,可能会直接使用类似代码,而不去思考它可能有问题。
Goroutine Race问题
Go语言支持函数中定义函数,看下一个例子:
func saveRequest(request *Request) { …. go func() { request.Users = []{1,2,3} …