当分配数个变量或者定义数个全局变量时,这些不同用途的变量很可能会被分配到很近的地址空间上,会导致CPU在读取时,他们会处在同一个Cache line中。如果这时一个CPU上的线程在操作其中一个变量,而另一个CPU上的线程在读另一个变量,将导致读线程CPU不得不和写线程CPU进行一次缓存同步后才能将数据读取出来,这会导致增加缓存开销和总线开销,降低程序效率。这就是并发编程中常碰到的False sharing的问题。详细内容可以参看这篇
还是拿上一篇文章的代码做测试:
#include "stdafx.h"
#include "windows.h"
PLONG pData1;
PLONG pData2;
DWORD
WINAPI
Thread1(
LPVOID lpThreadParameter
) {
DWORD Start = GetTickCount();
for (ULONG i = 0;i < 10000000;i++)
InterlockedAdd(pData1, 1);
printf("Time:%d\n", GetTickCount() - Start);
return 1;
}
DWORD
WINAPI
Thread2(
LPVOID lpThreadParameter
) {
for (ULONG i = 0;i < 10000000;i++)
InterlockedAdd(pData2, 1);
return 1;
}
int main()
{
DWORD Tid;
pData1 = (PLONG)malloc(sizeof(LONG) * 2);
pData2 = pData1 + 1;
CreateThread(NULL, 0, Thread1, NULL, 0, &Tid);
CreateThread(NULL, 0, Thread2, NULL, 0, &Tid);
system("pause");
return 0;
}
这里,两个变量被分配在了一起,可以肯定的是他们会处在同一个CacheLine中,这样两个线程对他们的操作将会产生伪共享。运行结果显示,这种代码运行的时间和上篇中第一个代码,既对同一个变量进行原子操作,需要的时间相近。所以伪共享问题会严重的影响程序的运行性能。
那么如何避免伪共享呢:
首先可以对多线程访问的变量按缓存行大小进行内存分配,这种做法固然会浪费一点内存空间,但可以避免伪共享的问题,不同的CPU对不同变量的操作将不会触发缓存同步。
其次也可以进行读写分离,如果一个结构中部分字段只会对其进行读操作,那么这些可以分配在一起,他们不会产生伪共享,而将写数据单独分配。