Go语言的并发，就不得不提goroutine，其作为Go语言的一大特色，在日常开发中使用很多Go goroutine理解这里就不对goroutine进行描述了，直接进入正文go并发控制方法主要有：公共变量channelWaitGroupcontext公共变量这是并发控制最简单的实现方式声明一个公共变量所有子goroutine共享这个变量，并不断轮询这个变量并检查是否有更新在主进程中变更该公共变量子goroutine检测到公共变量更新，执行相应的逻辑package main import ( "fmt" "time" ) func main() { // 公共变量 state := true go func(){ for state { println("goroutine A running") time.Sleep(1 * time.Second) } println("goroutine A exit") }() go func() { for state { println("goroutine B running") time.Sleep(1 * time.Second) } println("goroutine B exit") }() time.Sleep(2 * time.Second) state = false time.Sleep(2 * time.Second) fmt.Println("main func exit") }输出结果goroutine B running goroutine A running goroutine A running goroutine B running goroutine B running goroutine A running goroutine A exit goroutine B exit main func exit这种实现方式比较简单，但如果逻辑过于复杂，则变量不容易控制channelchannel是goroutine之间主要的通讯方式，一般会和select搭配使用Go channel 实现原理分析channel在这里也不做过多描述，大致处理过程为：声明一个可以判断结束的chan在goroutine中，使用select判断chan接收到的值。如果接收到结束，就可以走结束逻辑。如果没有，那么就会执行预设的逻辑，或者default主程序发送了结束的指令后子goroutine接到结束指令，进行结束逻辑package main import ( "fmt" "time" ) func main() { // 初始化一个接收bool的channel c := make(chan bool) go func() { for { select { case <- c: fmt.Println("goroutine exit") return default: fmt.Println("goroutine running.") } time.Sleep(100 * time.Millisecond) } }() time.Sleep(1 * time.Second) c <- false fmt.Println("main fun exit") }输出结果goroutine running. goroutine running. goroutine running. goroutine running. goroutine running. goroutine running. goroutine running. goroutine running. goroutine running. goroutine running. goroutine exit main fun exit这种方法比较优雅，但稍微复杂如果要控制多个goroutine，或者goroutine嵌套，就比较麻烦WaitGroupGo语言提供同步包syncsync包同步提供基本的同步原语，如互斥锁。除了Once和WaitGroup类型之外，大多数类型都是供低级库例程使用的。通过Channel和沟通可以更好地完成更高级别的同步。并且此包中的值在使用过后不要拷贝。Sync.WaitGroup是一种实现并发控制方式，WaitGroup 对象内部有一个计数器，最初从0开始，它有三个方法：Add(), Done(), Wait() 用来控制计数器的数量。Add(n) 把计数器设置为nDone() 每次把计数器-1wait() 会阻塞代码的运行，直到计数器地值减为0package main import ( "fmt" "sync" "time" ) func main() { fmt.Println(time.Now().Format("2006-01-02 15:04:05")) //定义一个WaitGroup var synWait sync.WaitGroup //计数器设置为2 synWait.Add(2) go func() { time.Sleep(1 * time.Second) fmt.Println("goroutine A finish |", time.Now().Format("2006-01-02 15:04:05")) //计数器减1 synWait.Done() }() go func() { time.Sleep(3 * time.Second) fmt.Println("goroutine B finish |", time.Now().Format("2006-01-02 15:04:05")) //计数器减1 synWait.Done() }() //会阻塞代码的运行，直到计数器地值减为0。 synWait.Wait() fmt.Println("main fun exit |", time.Now().Format("2006-01-02 15:04:05")) }输出结果2019-12-28 01:27:02 goroutine A finish | 2019-12-28 01:27:03 goroutine B finish | 2019-12-28 01:27:05 main fun exit | 2019-12-28 01:27:05可以看出A在1秒后输出，B在3秒后输出这种方法适合多个goroutine做同一个，或者差不多事情的时候 因为每个goroutine做的都是这件事情的一部分，只有全部的goroutine都完成，这件事情才算是完成，这是等待的方式。WaitGroup相对于channel并发控制方式比较轻巧context应用场景：在 Go http 包的 Server 中，每个Request都需要开启一个goroutine做一些事情，这些goroutine又可能会开启其他的goroutine 所以我们需要一种可以跟踪goroutine的方案，才可以达到控制他们的目的，这就是Go语言为我们提供的Context，称之为上下文控制并发的实现方式：context.Background():返回一个空的Context，这个空的Context一般用于整个Context树的根节点context.WithCancel(context.Background())，创建一个可取消的子Context，然后当作参数传给goroutine使用，这样就可以使用这个子Context跟踪这个goroutine在goroutine中，使用select调用<-ctx.Done()判断是否要结束，如果接收到值的话，就可以返回结束goroutine了。如果接收不到，就会继续进行监控cancel()，取消函数（context.WithCancel()返回的第二个参数，名字和声明的名字一致）。作用是给goroutine发送结束指令package main import ( "fmt" "time" "context" ) func main() { //创建一个可取消子context,context.Background():返回一个空的Context，这个空的Context一般用于整个Context树的根节点 ctx, cancel := context.WithCancel(context.Background()) go func(ctx context.Context) { for { select { //使用select调用<-ctx.Done()判断是否要结束 case <-ctx.Done(): fmt.Println("goroutine exit") return default: fmt.Println("goroutine running.") time.Sleep(100 * time.Millisecond) } } }(ctx) time.Sleep(1 * time.Second) //取消context cancel() time.Sleep(1 * time.Second) fmt.Println("main fun exit") }输出结果goroutine running. goroutine running. goroutine running. goroutine running. goroutine running. goroutine running. goroutine running. goroutine running. goroutine running. goroutine running. main fun exit如果想控制多个goroutine ，也很简单package main import ( "fmt" "time" "context" ) func main() { //创建一个可取消子context,context.Background():返回一个空的Context，这个空的Context一般用于整个Context树的根节点。 ctxA, cancelA := context.WithCancel(context.Background()) ctxB, cancelB := context.WithCancel(context.Background()) go func(ctx context.Context) { for { select { //使用select调用<-ctx.Done()判断是否要结束 case <- ctx.Done(): fmt.Println("goroutine A exit") return default: fmt.Println("goroutine A running.") time.Sleep(200 * time.Millisecond) } } }(ctxA) go func(ctx context.Context) { for { select { //使用select调用<-ctx.Done()判断是否要结束 case <- ctx.Done(): fmt.Println("goroutine B exit") return default: fmt.Println("goroutine B running.") time.Sleep(200 * time.Millisecond) } } }(ctxA) go func(ctx context.Context) { for { select { //使用select调用<-ctx.Done()判断是否要结束 case <- ctx.Done(): fmt.Println("goroutine C exit") return default: fmt.Println("goroutine C running.") time.Sleep(200 * time.Millisecond) } } }(ctxB) time.Sleep(1 * time.Second) //取消context cancelA() cancelB() time.Sleep(1 * time.Second) fmt.Println("main fun exit") }输出结果：goroutine A running. goroutine C running. goroutine B running. goroutine A running. goroutine C running. goroutine B running. goroutine A running. goroutine C running. goroutine B running. goroutine A running. goroutine C running. goroutine B running. goroutine A running. goroutine C running. goroutine B running. goroutine A exit goroutine C exit goroutine B exit main fun exit至于context的具体用法，请参考深度解密Go语言之context参考文档Go goroutine理解Go channel 实现原理分析深度解密Go语言之contextGo 并发控制GO语言并发编--sync包之WaitGroup的使用Go Context的踩坑经历