head false sharing我们一般说的是多核的问题,这个问题主要出现在CPU的缓存上,我们知道CPU有多级缓存,而CPU缓存单单位是行(主流一个缓存行是64Byte, 也就是8个int64)

CPU加载缓存

当我们要操作一个变量A时会把A附近的64个字节都加载到缓存行中(空间局部性原理),这样在CPU的缓存里操作时要比在内存中要快

多核CPU缓存问题

在多核心中每个CPU核心都有自己的缓存,如果A和B变量是挨着的, 当CPU1要写A变量, CPU2读变量B, 因为AB挨着,CPU1和CPU2都把它们加载到自己的缓存中,并且AB在同一个缓存行中,CPU1改了A导致了CPU2中B缓存失效,CPUB就得重新从内存中加载缓存

这种情况就是"假共享"/“伪共享”/“false sharing”

说白了就是,CPU各自都有一份,因为邻近变量修改导致了其他核心缓存失效

例子

CASE1

type FS struct {
	X int64
	Y int64
}

func share() {
	var a FS
	wg := sync.WaitGroup{}
	wg.Add(2)
	start := time.Now()
	go func() {
		for i := 0; i < 100000000; i++ {
			a.X++
		}
		wg.Done()
	}()
	go func() {
		for i := 0; i < 100000000; i++ {
			a.Y++
		}
		wg.Done()
	}()
	wg.Wait()
	fmt.Println(time.Since(start))
}

这种就创造了两个变量挨着在同一缓存行的情况,这个代码在我本机运行时间为305.462012ms

CASE 2

我们把上面结构体改一下,X和Y间隔56个字节, 使他们不在一个缓存行, 其他部分不变

type FS struct {
	X int64
	_ [7]int64
	Y int64
}

本地运行时间变为201.209402ms, 节省了100ms

注意区分false sharingdata race

  1. false sharing讲的是多个核心数据共享缓存失效的问题, 只是影响程序性能,并不影响程序正确性(解决这个问题可以使用内存填充,就像CASE2那样)
  2. data race讲的是并发访问同一个变量问题, 会导致意外的情况, 程序不能正确执行(没有安全的数据竞争,发生数据竞争要使用同步原语解决)