Content #
伪共享(false-sharing)的意思是说,当两个线程同时各自修改两个相邻的变量,由于缓存是按缓存块来组织的,当一个线程对一个缓存块执行写操作时,必须使其他线程含有对应数据的缓存块无效。这样两个线程都会同时使对方的缓存块无效,导致性能下降。
例子:
#include <stdio.h>
#include <pthread.h>
struct S{
long long a;
long long b;
} s;
void *thread1(void *args)
{
for(int i = 0;i < 100000000; i++){
s.a++;
}
return NULL;
}
void *thread2(void *args)
{
for(int i = 0;i < 100000000; i++){
s.b++;
}
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t t1, t2;
s.a = 0;
s.b = 0;
pthread_create(&t1, NULL, thread1, NULL);
pthread_create(&t2, NULL, thread2, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("a = %lld, b = %lld\n", s.a, s.b);
return 0;
}
在这个例子中,main 函数中创建了两个线程,分别修改结构体 S 中的 a 、b 变量。a 、b 均为 long long 类型,都占 8 字节,所以 a 、b 在同一个 cache line 中,因此会发生为伪共享的情况。
解决伪共享的办法是,将 a 、b 不要放在同一个 cache line,这样两个线程分别操作不同的 cache line 不会相互影响。具体来讲,我们需要对结构体 S 做出如下修改:
struct S{
long long a;
long long nop_0;
long long nop_1;
long long nop_2;
long long nop_3;
long long nop_4;
long long nop_5;
long long nop_6;
long long nop_7;
long long b;
} s;
因为在 a、b 中间插入了 8 个 long long 类型的变量,中间隔了 64 字节,所以 a、b 会被映射到不同的缓存块,性能有一倍的提升。
其实,伪共享是一种典型的缓存缺失问题,在并发场景中很常见。在 Java 的并发库里经常会看到为了解决伪共享而进行的数据填充。这是大家在写并发程序时也要加以注意的。