Go并发通信
并发中的资源竞争
原子性
原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉。即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。
示例和分析
如果两个或者多个goroutine在没有相互同步的情况下,访问某个共享资源比如同时对该资源进行读写时,就会处于相互竞争的状态,这就是并发中的资源竞争。
var (
count int32
wg sync.WaitGroup//这个后面讲锁时讲
)
func main() {
wg.Add(2)//这个后面讲锁时讲
go incCount()
go incCount()
wg.Wait()
fmt.Println(count)//这个后面讲锁时讲
}
func incCount() {
defer wg.Done()//这个后面讲锁时讲
for i := 0; i < 2; i++ {
value := count
// runtime.Gosched() 是让当前 goroutine 暂停的意思,退回执行队列,让其他等待的 goroutine 运行,目的是为了使资源竞争的结果更明显。
runtime.Gosched()
value++
count = value
}
}
这是一个资源竞争的例子,大家可以将程序多运行几次,会发现结果可能是 2,也可以是 3,还可能是 4。这是因为 count 变量没有任何同步保护,所以两个 goroutine 都会对其进行读写,会导致对已经计算好的结果被覆盖,以至于产生错误结果。
下面我们来分析一下这个例子
- g1读取到count的值为0
- 然后g1暂停了,切换到g2运行,g2读取到count的值也为0
- g2暂停,切换到g1,g1对 count+1,count的值为1
- g1暂停,切换到g2,g2刚刚已经获取到值0,对其+1,最后赋值给count,其结果还是1
- 可以看出g1对count+1的结果被g2给覆盖了,两个goroutine 都 +1 而结果还是 1
通过上面的分析可以看出,之所以出现上面的问题,是因为两个 goroutine 相互覆盖结果。
所以我们对于同一个资源的读写必须是原子性的,也就是说,同一时间只能允许有一个 goroutine 对共享资源进行读写操作。
如何检查资源竞争
共享资源竞争的问题,非常复杂,并且难以察觉,好在 Go 为我们提供了一个工具帮助我们检查,这个就是go build -race命令。在项目目录下执行这个命令,生成一个可以执行文件,然后再运行这个可执行文件,就可以看到打印出的检测信息。