2008-09-10 102 views
69

我有两个线程,一个更新int和一个读取它。这是一个统计值,其中读取和写入的顺序是不相关的。是C++读取和写入一个int原子?

我的问题是,我是否需要同步访问这个多字节值呢?换句话说,可以将部分写入完成并中断,然后读取就会发生。

例如,将value = 0x0000FFFF的值设为0x00010000。

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

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

+4

真的吗?我不在乎社区的想法。我会关心事实是什么:) – sehe 2011-09-28 18:22:11

+1

有趣的阅读话题:http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1- of-2 – ereOn 2013-07-03 09:01:41

+0

特别针对`=`:http://stackoverflow.com/questions/8290768/is-assignment-operator-atomic – 2015-06-16 10:53:16

回答

39

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

+0

ACE_Atomic_Op的文件已经移动 - 现在可以在http://www.dre.vanderbilt.edu/~schmidt/DOC_ROOT/ACE/ace/Atomic_Op.inl找到 – Byron 2011-12-09 16:41:47

0

不,他们不是(或者至少你不能假设他们是)。话虽如此,有一些技巧可以做到这一点,但它们通常不是便携式的(参见Compare-and-swap)。

8

是的,你需要同步访问。在C++ 0x中它将是一个数据竞争和未定义的行为。使用POSIX线程它已经是未定义的行为。

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

3

您必须同步,但在某些体系结构中存在有效的方法。

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

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

9

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

+2

这是在哪里的英特尔架构软件开发人员手册中说明的? – 2011-01-03 16:56:16

+1

@DanielTrebbien:也许参见http:// stackoverflow。com/questions/5002046/atomicity-in-c-myth-or-reality – sehe 2011-09-28 18:23:39

57

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

是的,没有,嗯,嗯,这取决于

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

另外,如果您正在使用C/C++不要忘记标记的共享价值波动,否则优化器会认为变量永远不会在你的一个线程更新。

+14

volatile关键字在多线程程序中无用http://stackoverflow.com/questions/2484980/why-is-volatile-not-considered-useful -in-multithreaded-c-or-c-programming – 2012-07-05 12:13:38

+3

@IngeHenriksen:我不相信那个链接。 – Skizz 2012-07-05 13:12:27

0

我同意许多,尤其是Jason。在Windows上,可能会使用InterlockedAdd及其朋友。

1

为了回应大家在楼上所说的话,C++ 0x之前的语言不能保证任何有关从多个线程访问共享内存的内容。任何保证将由编译器决定。从上面提到的缓存问题

0

Asside ...

如果代码移植到一个较小的寄存器大小的处理器也不会是原子了。

国际海事组织,线程问题太棘手以至于冒险。

3

在Windows上,互锁***交换***添加保证是原子的。

-1

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

0

让我们这个例子中

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

第一条语句被假定为原子,因为它转换为单个INC组件指令采用单个CPU周期。但是,第二个任务需要几个操作,所以显然不是原子操作。

另一个e.g,

x=5; 

同样,你必须拆解代码,看看到底发生了什么这里。

0

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

0

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

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

若要增加一个int,编译器首先将其加载到寄存器中,并且将其存储回所述存储器。这不是原子的。