多协程并发和多核CPU、协程空间

  • Post author:
  • Post category:其他


先看看下面这个程序的执行结果,多执行几次。

package main

import (
	"fmt"
	"sync"
)

var num int64 = 0
var max = 10000
var wg sync.WaitGroup

func main() {
	wg.Add(2)
	go addNum()
	go addNum()
	wg.Wait()
	fmt.Printf("num=%d \n", num)
}

func addNum() {
	for i := 0; i < max; i++ {
		num++
	}
	wg.Done()
}

输出结果不是一个定值,并且还有小于“max”变量值的输出。

为什么会这样,下面一步步剖析。

首先理解一下 num++ 在cpu层面的执行,它不是原子操作:

它是这样的:

int x = num;

x = x + 1;

num= x;

(这里x代表寄存器,amount代表内存)

我们假设你只有1个cpu,也就是执行本身不是并发的,但是有两个协程

假设num= 0

协程1

int x = num;

此时x = 0, num=0

切换到协程2

注意,x是寄存器,每个协程独立,而num是共享的,只有一个,所以为了避免混淆,这里x写成x1表示另一个变量

int x1 = num;

此时x1=0 num=0

x1 = x1 + 1;

此时x1=1

再切换到协程1

x = x + 1;

num= x;

此时x = 1

num=1

再切换到协程2

num= x1;

记住,x1=1

所以num=1

结果是1而不是2

为什么这样?因为切换的过程中x存的数据是脏数据,amount被另一个线程更新了,但是x没有更新。

每个协程都有自己独立的协程空间,指的是变量 i 在以上两个协程里面各自有一份。每个cup核都有自己的寄存器会缓存正在执行的变量,每个核还有多层Cache,这种情况下出现脏数据就不奇怪了。想让这个程序正确执行,可以使用原子指令。


https://www.usenix.org/conference/osdi10/ad-hoc-synchronization-considered-harmful


https://software.intel.com/en-us/blogs/2013/01/06/benign-data-races-what-could-possibly-go-wrong



版权声明:本文为kesenzhang原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。