我有两个线程,一个更新一个int,另一个读取它。这是一个统计值,其中读写顺序无关紧要。
我的问题是,我是否需要同步访问此多字节值?或者换句话说,写入的一部分可以完成并被中断,然后读取就会发生。
例如,假设值= 0x0000FFFF,其值增加为0x00010000。
我应该担心这个值看起来像0x0001FFFF吗?当然,类型越大,就越有可能发生这样的事情。
我总是同步这些类型的访问,但很好奇社区的想法。
答案 0 :(得分:64)
男孩,真是个问题。答案是:
是的,不,嗯,这取决于
这一切都归结为系统的架构。在IA32上,正确对齐的地址将是原子操作。未对齐的写入可能是原子的,它取决于正在使用的缓存系统。如果内存位于单个L1缓存行中,则它是原子的,否则不是。 CPU和RAM之间的总线宽度会影响原子性质:8086上正确对齐的16位写入是原子的,而8088上的相同写入不是因为8088只有8位总线而8086有16位总线。
此外,如果您正在使用C / C ++,请不要忘记将共享值标记为volatile,否则优化器会认为该变量永远不会在您的某个线程中更新。
答案 1 :(得分:44)
起初人们可能认为本机大小的读取和写入是原子的,但是有许多问题要处理,包括处理器/核心之间的缓存一致性。在Windows上使用Interlocked *等原子操作,在Linux上使用等效操作。 C ++ 0x将有一个“原子”模板将它们包装在一个漂亮的跨平台接口中。目前,如果您使用的是平台抽象层,它可能会提供这些功能。 ACE确实如此,请参阅课程模板ACE_Atomic_Op。
答案 2 :(得分:11)
如果您正在读/写4字节值并且它在内存中是DWORD对齐的并且您在I32架构上运行,则读取和写入都是原子的。
答案 3 :(得分:8)
是的,您需要同步访问。在C ++ 0x中,它将是一个数据争用和未定义的行为。对于POSIX线程,它已经是未定义的行为。
实际上,如果数据类型大于本机字大小,则可能会得到错误的值。此外,由于优化移动读取和/或写入,另一个线程可能永远不会看到写入的值。
答案 4 :(得分:3)
您必须同步,但在某些架构上,有一些有效的方法可以实现。
最好是使用子程序(可能掩盖在宏后面),以便您可以有条件地用特定于平台的实现替换实现。
Linux内核已经有了一些代码。
答案 5 :(得分:3)
在Windows上,Interlocked *** Exchange ***保证是原子的。
答案 6 :(得分:1)
为了回应每个人在楼上说的话,C ++ 0x之前的语言无法保证来自多个线程的共享内存访问。任何保证都取决于编译器。
答案 7 :(得分:1)
肯定不会!
来自我们最高C ++权威的答案,M。Boost:
的 Operations on "ordinary" variables are not guaranteed to be atomic. 强>
答案 8 :(得分:0)
不,他们不是(或者至少你不能认为他们是)。话虽如此,有些技巧可以原子地执行此操作,但它们通常不可移植(请参阅Compare-and-swap)。
答案 9 :(得分:0)
我同意很多人,特别是Jason。在Windows上,人们可能会使用InterlockedAdd及其朋友。
答案 10 :(得分:0)
从上面提到的缓存问题中解释出来......
如果将代码移植到具有较小寄存器大小的处理器,则它将不再是原子的。
IMO,线程问题太棘手了,无法承担风险。答案 11 :(得分:0)
让我们举个例子
int x;
x++;
x=x+5;
假设第一个语句是原子语句,因为它转换为单个INC程序集指令,它占用一个CPU周期。但是,第二个赋值需要几个操作,所以它显然不是原子操作。
另一个例如,
x=5;
同样,你必须反汇编代码才能看到这里到底发生了什么。
答案 12 :(得分:0)
TC, 我认为当你使用常量(如6)时,指令不会在一个机器周期内完成。 与x ++
相比,尝试查看x + = 6的指令集答案 13 :(得分:0)
有些人认为++ c是原子的,但是要关注生成的汇编。例如,使用'gcc -S':
movl cpt.1586(%rip), %eax
addl $1, %eax
movl %eax, cpt.1586(%rip)
要增加int,编译器首先将其加载到寄存器中,然后将其存储回内存中。这不是原子的。
答案 14 :(得分:0)
读取和写入是原子的,但您还需要担心编译器重新排序您的代码。编译器优化可能会违反代码中语句的先发生关系。通过使用 atomic,您不必担心。 ... 原子 i;
soap_status = GOT_RESPONSE ; 我 = 1
在上面的例子中,变量'i'只会在我们得到soap响应后设置为1。
答案 15 :(得分:-1)
唯一可移植的方法是使用signal.h头中为编译器定义的sig_atomic_t类型。在大多数C和C ++实现中,这是一个int。然后将变量声明为“volatile sig_atomic_t。”