相信进入Go语言世界的每一个人,一定会被其强大的并发(Concurrency)所吸引。Go语言用最简单的关键字go就可以把任务抛到后台,但是开发者如何有效的控制并发是Go语言初学者必学的技能。本章将介绍几种方式来帮助大家理解并发,这三种方式分别对应三个不同的名词:WaitGroup、Channel、Context。下面是一个简单的例子来告诉你。WaitGroup首先我们了解一下什么情况下需要使用WaitGroup。假设你有两台机器需要同时上传最新的代码。两台机器分别上传后,就可以进行最后的重启步骤了。就好比把一个工作同时拆分成几个部分,这样可以减少时间,但是最后需要等到全部都完成了,才能执行下一步。这时候就需要使用WaitGroup来做。packagemainimport("fmt""sync")funcmain(){varwgsync.WaitGroupi:=0wg.Add(3)//taskcountwaittodogofunc(){deferwg.Done()//finishtask1fmt.Println("goroutine1done")i++}()gofunc(){deferwg.Done()//finishtask2fmt.Println("goroutine2done")i++}()gofunc(){deferwg.Done()//finishtask3fmt.Println("goroutine3done")i++}()wg.Wait()//waitfortaskstobedonefmt.Println("allgoroutinedone")fmt.Println(i)}Channel另一个实际案例是我们需要主动通知一个Goroutine停止。也就是说,当App启动的时候,后台会运行一些监控程序,在需要停止整个App之前,需要先给后台的监控程序发送一个Notification,让其停止。这时候就需要一个Channel来通知。看下面的例子:packagemainimport("fmt""time")funcmain(){exit:=make(chanbool)gofunc(){for{select{case<-exit:fmt.Println("Exit")returncase<-time.After(2*time.Second):fmt.Println("Monitoring")}}}()time.Sleep(5*time.Second)fmt.Println("NotifyExit")exit<-true//keepmaingoroutinealivetime.Sleep(5*time.Second)}可以发现上面的例子,使用了一个Gogourtine和Channel来控制。可以想象,当后台有无数个Goroutines时,我们需要用多个Channel来控制它们。也许Goroutines会在Goroutines中生成。这时候,开发者会发现不能再简单的通过Channels来控制多个Goroutines了。这时候解决办法就是传Context。背景可以想象,今天有一个后台任务A,任务A生成任务B,任务B生成任务C,也就是可以按照这个模式继续生成,假设我们需要中途停止任务A,并且A必须告诉B和C一起停下来。这时候使用context是最快的方式。packagemainimport("context""fmt""time")funcfoo(ctxcontext.Context,namestring){gobar(ctx,name)//AcallsBfor{select{case<-ctx.Done():fmt.Println(name,"AExit")returncase<-time.After(1*time.Second):fmt.Println(name,"Adosomething")}}}funcbar(ctxcontext.Context,namestring){for{select{case<-ctx.Done():fmt.Println(name,"BExit")returncase<-time.After(2*time.Second):fmt.Println(name,"Bdosomething")}}}funcmain(){ctx,cancel:=context.WithCancel(context.Background())gofoo(ctx,"FooBar")fmt.Println("clientreleaseconnection,needtonotifyA,Bexit")time.Sleep(5*time.Second)cancel()//模拟clientexit,andpassthesignal,ctx.Done()getsthesignaltime.Sleep(3*time.Second)time.Sleep(3*time.Second)}packagemainimport("context""fmt""time")funcfoo(ctxcontext.Context,namestring){gobar(ctx,name)//AcallsBfor{select{case<-ctx.Done():fmt.Println(name,"AExit")返回case<-time.After(1*time.Second):fmt.Println(name,"Adosomething")}}}funcbar(ctxcontext.Context,namestring){for{select{case<-ctx.Done():fmt.Println(name,"BExit")returncase<-time.After(2*time.Second):fmt.Println(name,"Bdosomething")}}}funcmain(){ctx,cancel:=context.WithCancel(context.Background())gofoo(ctx,"FooBar")fmt.Println("clientreleaseconnection,needtonotifyA,Bexit")time.Sleep(5*time.Second)cancel()//模拟客户端退出,并传递信号,ctx.Done()获取信号time.Sleep(3*time.Second)time.Sleep(3*time.Second)}可以把context想成一个controller,它可以随时控制不确定数量的Goroutines,从上到下,只要声明了context.WithCancel,整个后台服务可以在任意时间点被cancel()停止,实际情况会在App需要重启的时候使用,所有goroutines必须通知先停止,正常停止后,App会重启。。总结根据不同的情况和条件选择不同的方法,做一个总结:WaitGroup:需要将单个任务分解成多个子任务,等待全部完成后再进行下一步。这时候使用WaitGroup最适合Channel+Select:Channel只能在Goroutine比较简单的情况下使用。如果要管理多个Goroutine,推荐使用context。
