伪共享及其解决办法

118 阅读2分钟

伪共享

Cache 伪共享(Cache False Sharing)指的是在多核处理器中,由于多个线程同时访问不同的变量或对象,但这些变量或对象又被映射到了同一个缓存行中,导致多个线程之间频繁地竞争同一缓存行,造成缓存竞争,从而影响程序的性能。

举个例子,假设有两个线程 A 和 B,它们分别访问两个不同的变量 X 和 Y,但这两个变量被映射到了同一个缓存行中。当线程 A 修改变量 X 的值时,由于缓存一致性协议的机制,缓存行会被标记为“脏”行,导致线程 B 也无法访问缓存行中的变量 Y,因为缓存行需要被刷新回内存。这就会导致线程 B 在等待缓存行刷新的过程中浪费了大量的时间,从而影响程序的性能。

为了避免 Cache 伪共享,可以采用以下几种方法:

  1. 让不同的变量或对象映射到不同的缓存行中,可以通过填充空间的方式来实现。
  2. 使用缓存行对齐的方式,保证不同变量或对象的起始地址都是缓存行的整数倍,从而避免多个变量或对象被映射到同一个缓存行中。
  3. 采用写时复制(Copy-on-write)技术,将共享的数据拷贝一份到每个线程的私有缓存中,从而避免多个线程之间竞争同一缓存行。

go避免伪共享

在 Go 中,可以通过使用结构体的填充来避免 Cache 伪共享。具体来说,可以在结构体的字段中添加无用的填充字段来确保每个字段都位于不同的缓存行中。例如,可以使用下划线(_)来填充结构体字段,例如:

type MyStruct struct {
    field1 int64
    _      [56]byte // 填充 56 字节的空间
    field2 int64
}

在上面的代码中,field1field2 都被分别映射到不同的缓存行中,从而避免了 Cache 伪共享的问题。

另外,在 Go 1.8 版本中,标准库中添加了 sync/atomic 包中的 CacheLinePad 类型,用于填充缓存行。例如:

import "sync/atomic"

type MyStruct struct {
    field1 int64
    _      [atomic.CacheLinePad - 8]byte // 填充一个缓存行
    field2 int64
}

这样可以确保 field1field2 分别位于不同的缓存行中,从而避免 Cache 伪共享的问题。