语境:我在用POSIX 共享内存为一组进程提供共享内存空间。我已经使用这个方案一段时间了,以便共享数据,并且工作正常。然而,我最近在某类程序中遇到了一个奇怪的问题。
问题:我编写了一个程序,其中每个进程都必须为共享内存空间中的共享总和贡献一个值。当共享对象早些时候映射到内存时,总和被初始化为零。然而,当每个进程尝试将其部分添加到共享总和中时,它可以看到最新的值,但是加法的结果总是像它自己的值加上零。见下文:
[21017] Adding 6 to 0!
[21020] Adding 33 to 0!
[21016] Adding 15 to 0!
[21018] Adding 24 to 0!
[21017] Got access! (0x7fe953fcb000 = 0)
[21017] Done (0x7fe953fcb000 = 6)
[21016] Got access! (0x7fe953fcb000 = 6)
[21016] Done (0x7fe953fcb000 = 15)
[21018] Got access! (0x7fe953fcb000 = 15)
[21018] Done (0x7fe953fcb000 = 24)
[21020] Got access! (0x7fe953fcb000 = 24)
[21020] Done (0x7fe953fcb000 = 33)
Sum = 33
每个进程“看到”写入的最新值,但不知何故,在添加其自己的组件后,似乎忽略了现有值。您可以看到每次访问都是按顺序排序的,因为有一个访问控制系统管理谁可以写入共享内存空间。使用的测试程序如下所示(尽管我不希望读者运行它):
int main (void) {
int local_sum = 0, gid = -1;
volatile int *sum;
// Fork for four processes.
for (int i = 1; i < 4; i++) {
if (fork() == 0) break;
}
// Initialize the DSM. Set GID.
sum = (int *)dsm_init(&cfg);
gid = dsm_get_gid();
// Compute range.
for (int i = 0; i < 3; i++) {
local_sum += array[(gid * 3) + i];
}
// Add to local sum.
printf("[%d] Adding %d to %d!\n", getpid(), local_sum, *sum);
*sum = *sum + local_sum;
// Barrier.
dsm_barrier();
// Print sum if process zero.
if (gid == 0) printf("Sum = %d\n", *sum);
// Exit.
dsm_exit();
}
为什么每个进程都可以“看到”0x7fe953fcb000
共享空间中地址处的正确值,但在添加之后,表现得好像添加期间该地址处的值仍然为零?
以下是这个问题困扰我的地方:
- 如果是缓存问题,为什么我可以在算术运算之前打印正确的值,但它仍然不正确?
- 我正在添加进程堆上的共享值。编译器无法假设该值为零,并优化了任何内容。
对于为什么会发生这种情况有什么解释吗?我尝试在我的程序中使用 GDB 来看看发生了什么。但据我所知,它只是将内存地址处的值移动到寄存器中。我还没有看到任何优化问题。
答案1
据我所见,四个进程快速连续生成,每个进程都尝试执行 * sum += some_value; 操作。完全有可能他们都认为 * sum 在相加之前为零。
让我们使用抽象汇编语法。 C 语句
*sum = *sum + local_sum
被编译成
LOAD *sum into R0
LOAD local_sum into R1
ADD R1 to R0
STORE R0 to *sum
四个进程竞相执行该序列。完全有可能在它们中的任何一个有机会将 R0 存储到 *sum 之前,它们都将 LOAD *sum 存储到 R0 中;事实上,正如您所说,考虑到有一个由 STORE R0 触发的系统调用(因此是一个重新规划点)到 *sum,这是一个很好的机会。您需要同步对共享变量的访问,例如使用信号量。