Go 并发——实现协程同步的几种方式


前言
Java 中有一系列的线程同步的方法,go 里面有 goroutine(协程),先看下下面的代码执行的结果是什么呢?

package mainimport ( "fmt" )func main() { go func() { fmt.Println("Goroutine 1") }()go func() { fmt.Println("Goroutine 2") }() }

执行以上代码很可能看不到输出。
因为有可能这两个协程还没得到执行,主协程就已经结束了,而主协程结束时会结束所有其他协程,所以导致代码运行的结果什么都没有。
估计不少新接触 go 的童鞋都会对此郁闷?,可能会问那么该如何等待主协程中创建的协程执行完毕之后再结束主协程呢?
下面说几种可以解决的方法:
Sleep 一段时间
在 main 方法退出之前 sleep 一段时间就可能会出现结果了,如下代码:
package mainimport ( "fmt" "time" )func main() { go func() { fmt.Println("Goroutine 1") }()go func() { fmt.Println("Goroutine 2") }()time.Sleep(time.Second * 1) // 睡眠1秒,等待上面两个协程结束 }

这两个简单的协程执行消耗的时间很短的,所以你会发现现在就有结果出现了。
Goroutine 1 Goroutine 2

为什么上面我要说 “可能会出现” ?
因为 sleep 这个时间目前是设置的 1s,如果我这两个协程里面执行了很复杂的逻辑操作(时间大于 1s),那么就会发现依旧也是无结果打印出来的。
那么就可以发现这种方式得到问题所在了:我们无法确定需要睡眠多久
上面那种方式有问题,go 里面其实也可以用管道来实现同步的。
管道实现同步
那么用管道怎么实现同步呢?show code:
package mainimport ( "fmt" )func main() {ch := make(chan struct{}) count := 2 // count 表示活动的协程个数go func() { fmt.Println("Goroutine 1") ch <- struct{}{} // 协程结束,发出信号 }()go func() { fmt.Println("Goroutine 2") ch <- struct{}{} // 协程结束,发出信号 }()for range ch { // 每次从ch中接收数据,表明一个活动的协程结束 count-- // 当所有活动的协程都结束时,关闭管道 if count == 0 { close(ch) } } }

这种方式是一种比较完美的解决方案, goroutine / channel 它们也是在 go 里面经常搭配在一起的一对。
sync.WaitGroup
其实 go 里面也提供了更简单的方式 —— 使用 sync.WaitGroup。
WaitGroup 顾名思义,就是用来等待一组操作完成的。WaitGroup 内部实现了一个计数器,用来记录未完成的操作个数,它提供了三个方法:
  • Add() 用来添加计数
  • Done() 用来在操作结束时调用,使计数减一
  • Wait() 用来等待所有的操作结束,即计数变为 0,该函数会在计数不为 0 时等待,在计数为 0 时立即返回
继续 show code:
package mainimport ( "fmt" "sync" )func main() {var wg sync.WaitGroupwg.Add(2) // 因为有两个动作,所以增加2个计数 go func() { fmt.Println("Goroutine 1") wg.Done() // 操作完成,减少一个计数 }()go func() { fmt.Println("Goroutine 2") wg.Done() // 操作完成,减少一个计数 }()wg.Wait() // 等待,直到计数为0 }

【Go 并发——实现协程同步的几种方式】你会发现也是可以看到运行结果的,是不是发现这种方式是很简单的。
总结
多看别人写的代码;多想想为啥要这样写;多查自己不理解的地方;多写 demo 测试;多写文章总结。
关注我

本文地址为:http://www.54tianzhisheng.cn/2018/08/30/go-sync/ ,转载请注明原文出处!

    推荐阅读