87

我有两个线程,一个更新一个 int,一个读取它。这是一个统计值,其中读取和写入的顺序无关紧要。

我的问题是,我是否需要同步访问这个多字节值?或者,换一种说法,可以部分写入完成并被中断,然后读取发生。

例如,考虑一个值 = 0x0000FFFF,它的增量值为 0x00010000。

有没有我应该担心的值看起来像 0x0001FFFF 的时候?当然,类型越大,发生这种情况的可能性就越大。

我总是同步这些类型的访问,但很好奇社区的想法。

4

16 回答 16

63

男孩,什么问题。答案是:

是的,不,嗯,嗯,这取决于

这一切都归结为系统的架构。在 IA32 上,正确对齐的地址将是原子操作。未对齐的写入可能是原子的,它取决于使用的缓存系统。如果内存位于单个 L1 缓存行中,则它是原子的,否则不是。CPU 和 RAM 之间的总线宽度会影响原子性:8086 上正确对齐的 16 位写入是原子性的,而 8088 上的相同写入不是因为 8088 只有 8 位总线,而 8086 有16 位总线。

此外,如果您使用 C/C++,请不要忘记将共享值标记为 volatile,否则优化器会认为该变量永远不会在您的线程之一中更新。

于 2008-09-10T14:42:36.033 回答
48

一开始可能会认为本机机器大小的读取和写入是原子的,但有许多问题需要处理,包括处理器/内核之间的缓存一致性。在 Windows 上使用 Interlocked* 等原子操作,在 Linux 上使用等效操作。C++0x 将有一个“原子”模板来将它们包装在一个漂亮的跨平台界面中。目前,如果您使用平台抽象层,它可能会提供这些功能。 ACE确实如此,请参阅类模板ACE_Atomic_Op

于 2008-09-10T15:37:49.553 回答
11

如果您正在读取/写入 4 字节值并且它在内存中是 DWORD 对齐的并且您在 I32 架构上运行,那么读取和写入是原子的。

于 2008-09-10T14:41:34.367 回答
9

是的,您需要同步访问。在 C++0x 中,这将是一场数据竞争和未定义的行为。对于 POSIX 线程,它已经是未定义的行为。

在实践中,如果数据类型大于本机字长,您可能会得到错误的值。此外,由于移动读取和/或写入的优化,另一个线程可能永远不会看到写入的值。

于 2008-09-10T14:31:07.373 回答
3

您必须同步,但在某些架构上,有一些有效的方法可以做到这一点。

最好是使用子例程(可能隐藏在宏后面),以便您可以有条件地用特定于平台的实现替换实现。

Linux 内核已经有一些这样的代码。

于 2008-09-10T14:38:01.693 回答
3

在 Windows 上,Interlocked***Exchange***Add 保证是原子的。

于 2008-09-16T21:46:03.877 回答
2

呼应楼上大家所说的,C++0x 之前的语言不能保证任何关于多线程共享内存访问的事情。任何保证都取决于编译器。

于 2008-09-11T16:07:06.307 回答
0

不,它们不是(或者至少你不能假设它们是)。话虽如此,有一些技巧可以原子地执行此操作,但它们通常不可移植(请参阅Compare-and-swap)。

于 2008-09-10T14:30:12.913 回答
0

我同意许多人的观点,尤其是杰森。在 Windows 上,可能会使用 InterlockedAdd 及其朋友。

于 2008-09-10T15:00:19.533 回答
0

除了上面提到的缓存问题...

如果您将代码移植到具有较小寄存器大小的处理器,它将不再是原子的。

IMO,线程问题太棘手,不能冒险。

于 2008-09-12T01:03:40.433 回答
0

让我们举这个例子

int x;
x++;
x=x+5;

第一条语句被假定为原子语句,因为它转换为一个占用单个 CPU 周期的单个 INC 汇编指令。但是,第二个分配需要几个操作,所以它显然不是原子操作。

另一个例如,

x=5;

同样,您必须反汇编代码才能看到这里到底发生了什么。

于 2010-07-31T16:39:33.913 回答
0

tc,我认为当您使用常量(如 6)时,该指令不会在一个机器周期内完成。尝试查看 x+=6 与 x++ 相比的指令集

于 2010-08-19T09:22:09.967 回答
0

有些人认为 ++c 是原子的,但要注意生成的程序集。例如使用 'gcc -S' :

movl    cpt.1586(%rip), %eax
addl    $1, %eax
movl    %eax, cpt.1586(%rip)

为了增加一个 int,编译器首先将它加载到一个寄存器中,然后将它存储回内存中。这不是原子的。

于 2012-03-28T07:39:02.160 回答
0

绝对不!来自我们最高 C++ 权威 M. Boost 的回答:
不能保证对“普通”变量的操作是原子的。

于 2013-10-22T09:13:50.597 回答
-1

唯一可移植的方法是为您的编译器使用在 signal.h 头文件中定义的 sig_atomic_t 类型。在大多数 C 和 C++ 实现中,这是一个 int。然后将您的变量声明为“volatile sig_atomic_t”。

于 2010-05-09T01:47:06.663 回答
-1

读取和写入是原子的,但您还需要担心编译器会重新排序您的代码。编译器优化可能会违反代码中语句的先发生关系。通过使用 atomic,您不必担心这一点。... 原子 i;

肥皂状态 = GOT_RESPONSE ;我 = 1

在上面的例子中,变量'i'只有在我们得到一个soap响应后才会被设置为1。

于 2021-05-30T14:05:48.753 回答