本文摘自网络,作者,侵删。
go是一门很适合用来写并发的服务端语言,今天我们就来看看它是怎么处理并发的,和其它语言相比又有哪些优势。控制并发主要包括两种方式:一种是WaitGroup,另外一种是Context。
WaitGroup
WaitGroup是一种控制多个goroutine并发执行的方式
var wg sync.WaitGroup
func service1() {
time.Sleep(2*time.Second)
fmt.Println("service1 done")
wg.Done()
}
func service2() {
time.Sleep(2*time.Second)
fmt.Println("service2 done")
wg.Done()
}
func main() {
wg.Add(2)
go service1()
go service2()
wg.Wait()
fmt.Println("all done")
}
通常的后端服务来了一个request后要rpc调用后面的其它服务,对应于下面例子中的service1、service2,然后起了两个协程去并发处理。
这种控制并发的方式适用于多个goroutine协同做一件事情的时候,当全部的goroutine都完成,这件事情才算是完成。
上面的例子是协程内自己处理结束后调用wg.Done退出,实际使用中我们可能需要从外部去结束一个协程。不然它会一直跑,就泄漏了。
Channel
接着上面的问题如何从外部去结束一个goroutine,很容易想到的一个方法就是定义一个全局变量,然后再外部修改这个变量的值,goroutine不断的轮训这个变量是否改变。
这种方式也可以,但是首先我们要保证这个变量在多线程下的安全,基于此,有一种更好的方式:channel + select
func testChannel() {
stop := make(chan bool)
go func() {
for {
select {
case <-stop:
fmt.Println("goroutine done")
return
default:
fmt.Println("goroutine is running")
time.Sleep(2 * time.Second)
}
}
}()
time.Sleep(10 * time.Second)
fmt.Println("cancel goroutine")
stop<- true
//为了检测监控过是否停止,如果没有监控输出,就表示停止了
time.Sleep(5 * time.Second)
}
这种channel+select的方式,是比较优雅的结束一个goroutine的方式,不过这种方式也有局限性,如果有很多goroutine都需要控制结束怎么办呢?如果这些goroutine又衍生了其他更多的goroutine怎么办呢?如果一层层的无穷尽的goroutine呢?这就非常复杂了,即使我们定义很多chan也很难解决这个问题,因为goroutine的关系链就导致了这种场景非常复杂。
Context
Go 1.7 标准库引入 context可以很好的解决上面的问题。下面用context的方式改写上面的例子。
func testContext() {
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("goroutine done")
return
default:
fmt.Println("goroutine is running")
time.Sleep(2 * time.Second)
}
}
}(ctx)
time.Sleep(10 * time.Second)
fmt.Println("cancel goroutine")
cancel()
//为了检测监控过是否停止,如果没有监控输出,就表示停止了
time.Sleep(5 * time.Second)
}
context.Background() 返回一个空的Context,这个空的Context一般用于整个Context树的根节点。然后我们使用context.WithCancel(parent)函数,创建一个可取消的子Context,然后当作参数传给goroutine使用,这样就可以使用这个子Context跟踪这个goroutine。
相关阅读 >>
更多相关阅读请进入《Go》频道 >>
Go语言101
一个与时俱进的Go编程知识库。