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

我的问题是,我是否需要同步对这个多字节值的访问?或者,换句话说,部分写入可以完成并被中断,然后再进行读取。

例如,假设值 = 0x0000FFFF,其增量值为 0x00010000。

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

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

有帮助吗?

解决方案

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

其他提示

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

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

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

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

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

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

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

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

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

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

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

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

绝对不!这个答案来自我们的 C++ 最高权威 M。促进:
对“普通”变量的操作不保证是原子的。

不,它们不是(或者至少你不能假设它们是)。话虽如此,有一些技巧可以原子地完成此操作,但它们通常不可移植(请参阅 比较和交换).

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

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

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

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

让我们看这个例子

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

第一个语句被假定为原子的,因为它转换为占用单个 CPU 周期的单个 INC 汇编指令。但是,第二个赋值需要多个操作,因此它显然不是原子操作。

另一个例如

x=5;

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

TC,我认为当您使用常数(例如6)的那一刻,该指令将在一个机器周期中无法完成。尝试查看 x+=6 的指令集与 x++ 的比较

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

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

要递增 int,编译器首先将其加载到寄存器中,然后将其存储回内存中。这不是原子的。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top