伪共享(C示例)

伪共享(C示例)

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 的并发库里经常会看到为了解决伪共享而进行的数据填充。这是大家在写并发程序时也要加以注意的。

Viewpoint #

From #