Goroutine陷阱 - 蘭陵N散記

JerryXia 发表于 , 阅读 (0)
1797 字 ~4分钟

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}                      …