3

显然,原子操作确保不同的线程不会破坏一个值。但是,当使用共享内存时,跨进程是否仍然如此?即使这些进程碰巧被操作系统安排在不同的内核上运行?还是跨不同的不同 CPU?

编辑:另外,如果它不安全,即使在像 Linux 这样的操作系统上也不安全,从调度程序的角度来看,进程和线程是相同的?

4

1 回答 1

3

tl;dr:阅读原子操作文档中的细则。有些设计是原子的,但可能会跳过某些变量类型。但是,一般来说,原子操作将在不同进程之间保持其契约,就像它在线程之间所做的一样。

如果两个实体同时调用,原子操作实际上只能确保您不会出现不一致的状态。例如,由两个不同线程或进程在同一个整数上调用的原子增量将始终表现如下:

  1. x = 初始值(为了讨论,为零)
  2. 实体 A 增加 x 并将结果返回给自身:result = x = 1。
  3. 实体 B 增加 x 并将结果返回给自身:result = x = 2。

其中 A 和 B 表示进行调用的第一个和第二个线程或进程。

由于竞争条件、对地址空间的不完整写入等,非原子操作可能会导致不一致或通常疯狂的结果。例如,您可以很容易地看到:

  1. x = 初始值 = 再次为零。
  2. 实体 A 调用 x = x + 1。要计算 x + 1,A 检查 x 的值(零)并加 1。
  3. 实体 B 调用 x = x + 1。为了计算 x + 1,B 检查 x 的值(仍然为零)并加 1。
  4. 实体 B(幸运)首先完成并将 x + 1 = 1(步骤 3)的结果分配给 x。x 现在是 1。
  5. 实体 A 获得第二名并将 x + 1 = 1(步骤 2)的结果分配给 x。x 现在是 1。

请注意竞争条件,因为实体 B 越过 A 并首先完成表达式。

现在想象一下,如果 x 是一个 64 位双精度数,则不能保证具有原子分配。在这种情况下,您可以很容易地看到如下内容:

  1. 一个 64 位双精度 x = 0。
  2. 实体 A 尝试将 0x1122334455667788 分配给 x。首先分配前 32 位,将 x 保留为 0x1122334400000000。
  3. 实体 B 竞相进入并将 0xffeeddccbbaa9988 分配给 x。偶然地,两个 32 位的一半都更新了,现在 x = 0xffeeddccbbaa9988。
  4. 实体 A 完成其后半部分的分配,现在 x = 0xffeeddcc55667788。

这些非原子分配是您必须诊断的一些最可怕的并发错误。

于 2009-10-18T19:13:06.693 回答