sync包中的同步原语和channel的使用场景
题目来源:七牛
参考《GO 语言并发之道》
参考 http://www.zzvips.com/article/183814.html
Go 语言中的channel和临界区都是控制并发的一种手段,谈及并发,谈及并发,我们首先要了解竞争条件:
当两个或多个操作必须按正确的顺序执行,而程序并未保证这个顺序,就会发生竞争条件。大多数情况下,这将在所谓的数据中出现,其中一个并发操作尝试读取一个变量,而在某个不确定的时间,另一个并发操作试图写入同一个变量。
那什么是临界区呢?
程序中需要独占访问共享资源的部分就叫临界区。举个例子
var data int
go func(){
data++
}()
if data == 0{
fmt.Println("the value is 0.")
}else{
fmt.Printf("the value is %v.
",data)
}
在这个例子中我们有三个临界区
- goroutine正在使数据变量递增
- if 语句,他检查数据的值是否为0
- fmt.Printf语句,在检索并打印变量的值。
如何解决因为多个goroutine在同一地址空间上运行而产生的数据竞争问题:那就引入了我们的主角,1. 通过sync包的同步原语访问共享内存 2 使用CSP原语来通信共享内存。
sync包含的对低级别内存访问同步最有用的并发原语:
- WaitGroup(等待一组并发操作完成的方法,尚硅谷有视频称“集齐七颗龙珠召唤神龙”非常形象,可以想象它是一组并发安全的计数器)
- 互斥锁和读写锁
- Cond (一般可以认为是事件触发变量,提供signal 和 broadcast 两种方法,当达到某个条件的时候,触发唤醒单个或所有阻塞的goroutine)
- Once(确保即使在不同的goroutine上,也只会调用一次Do方法处理的函数)
Go重要的主角channel使用的场景
- 使用
chan struct{}
作为信号channel - 管道实现互斥锁(利用无缓冲通道的同步性质)
- 使用channel控制子协程( 父协程可以等待子协程结束,父协程也可以向管道中写入数据通知子协程结束,这时子协程需要定期地探测管道中是否有消息出现。)
注意: 使用channel来控制子协程的优点是实现简单,缺点是当需要大量创建协程时就需要有相同数量的channel,而且对于子协程继续派生出来的协程不方便控制。