我正在调试一个从多个线程写入数组的函数。作为MWE,它将每个元素设置为零并递增它,直到array[i]
包含i >> 13
。每个元素仅由单个线程修改。
但是当在写入期间另一个线程从数组中读取时,即使读取线程不使用该值,也不会更改数组的部分内容。
void test(int* array, int* backup, int length)
{
#pragma omp parallel
{
#pragma omp for schedule(static)
for(int i = 0; i<length; i++) {
#pragma omp atomic write
array[i] = 0;
int target = i >> 13;
int tmp = 0;
int j = i + 5000000;
while (array[i] < target) {
j+=15; if (j >= length) j = 0;
int r;
#pragma omp atomic read
r = array[j];
tmp += r;
#pragma omp atomic
array[i]++;
}
backup[i]=array[i] * 10 + (tmp & 1);
}
}
}
当该函数在例如使用0xABCD1234(-1412623820)初始化的1000万个元素阵列上运行时,array[1974732]
之后的20个元素变为11 -1412623820 -1412623820 -1412623820 -1412623820 -1412623820 -1412623820 -1412623820 -1412623820 -1412623820 -1412623820 -1412623820 -1412623820 -1412623820 -1412623820 -1412623820 -1412623820 241 241 241
。 (从不相同的受影响元素)
但是,未读数组backup[i]/10
始终包含正确的值,例如241所有上述元素。
似乎每个处理器在其缓存中写入正确的值,然后有时会忘记更新主内存。或者也许ram坏了而忽略了写作?它发生在64核AMD Opteron 6272上,问题不受gcc优化级别,更多刷新/围栏指令或原子编译指示的影响。
剩余测试代码:
int gbackup[10000000];
int main(int argc, char *argv[])
{
int length = 10000000;
int* seq = malloc(length * sizeof(length));
for (int i=0;i<length;i++)
seq[i] = i >> 13;
for (int a=1;a<500;a++)
{
int* par = malloc(length * sizeof(length));
for (int i=0;i<length;i++)
par[i] = gbackup[i] = 0xABCD1234;
parallel_implementation(par, gbackup, length);
if (memcmp(seq, par, length*sizeof(int)) != 0) {
printf("\nAborting.\n");
return -1;
}
}
return 0;
}
更新
实际上它似乎与读取无关。或许它变得更糟。即便这样现在也失败了,并且保留了不变或低于i>>13
的数组元素:
void test(int* array, int*, int length)
{
#pragma omp parallel
{
#pragma omp for schedule(static)
for(int i = 0; i<length; i++) {
#pragma omp atomic write
array[i] = 0;
int target = i >> 13;
while (array[i] < target) {
#pragma omp atomic
array[i]++;
}
}
}
}