C++ 对 int 的读写是原子的吗?
Posted
技术标签:
【中文标题】C++ 对 int 的读写是原子的吗?【英文标题】:Are C++ Reads and Writes of an int Atomic? 【发布时间】:2010-09-08 10:12:47 【问题描述】:我有两个线程,一个更新一个 int,一个读取它。这是一个与读写顺序无关的统计值。
我的问题是,我是否需要同步访问这个多字节值?或者,换一种说法,可以部分写入完成并被中断,然后再进行读取。
例如,考虑一个值 = 0x0000FFFF,它的增量值为 0x00010000。
是否存在我应该担心的值看起来像 0x0001FFFF 的时间?当然,类型越大,发生这种情况的可能性就越大。
我一直同步这些类型的访问,但很好奇社区的想法。
【问题讨论】:
真的吗?我不在乎社区怎么想。我会关心事实是什么:) 关于该主题的有趣阅读:channel9.msdn.com/Shows/Going+Deep/… 专为=
:***.com/questions/8290768/…
【参考方案1】:
不,它们不是(或者至少你不能假设它们是)。话虽如此,有一些技巧可以原子地执行此操作,但它们通常不可移植(请参阅Compare-and-swap)。
【讨论】:
【参考方案2】:是的,您需要同步访问。在 C++0x 中,这将是一场数据竞争和未定义的行为。对于 POSIX 线程,它已经是未定义的行为。
在实践中,如果数据类型大于本机字长,您可能会得到错误的值。此外,由于移动读取和/或写入的优化,另一个线程可能永远不会看到写入的值。
【讨论】:
【参考方案3】:您必须同步,但在某些架构上,有一些有效的方法可以做到这一点。
最好使用子例程(可能隐藏在宏后面),以便您可以有条件地用特定于平台的实现替换实现。
Linux 内核已经有一些这样的代码。
【讨论】:
【参考方案4】:如果您正在读取/写入 4 字节值并且它在内存中是 DWORD 对齐的并且您在 I32 架构上运行,那么读取和写入是原子的。
【讨论】:
英特尔架构软件开发人员手册中的什么地方对此进行了说明? @DanielTrebbien:也许见***.com/questions/5002046/… 这在 C++ 级别是不正确的。底层机器指令是原子的,但允许编译器以破坏原子性的方式进行优化。【参考方案5】:男孩,这是什么问题。答案是:
是的,不,嗯,好吧,这取决于
这一切都归结为系统的架构。在 IA32 上,正确对齐的地址将是原子操作。未对齐的写入可能是原子的,它取决于使用的缓存系统。如果内存位于单个 L1 缓存行中,则它是原子的,否则不是。 CPU 和 RAM 之间的总线宽度会影响原子性:8086 上正确对齐的 16 位写入是原子性的,而 8088 上的相同写入不是因为 8088 只有 8 位总线,而 8086 有16 位总线。
此外,如果您使用的是 C/C++,请不要忘记将共享值标记为 volatile,否则优化器会认为该变量永远不会在您的某个线程中更新。
【讨论】:
volatile关键字在多线程程序中没用***.com/questions/2484980/… @IngeHenriksen:我不相信那个链接。 另一个来源,但不幸的是非常古老(它早于 std::atomic):web.archive.org/web/20190219170904/https://software.intel.com/… 这个答案已经过时了。从 C++11 开始,对不是std::atomic<int>
的 int 的非同步访问是数据竞争,并且是未定义的行为。所以目前的正确答案是平no。
@NateEldredge:这并不完全正确,未定义并不意味着“完全不”。正如我在回答中所说,“这取决于”。未定义只是意味着没有保证操作是原子的,有时是,有时不是。如果您对原子性质做出假设,则代码将无法移植,但如果您针对固定的硬件和软件配置并适当地注释代码,那么该假设可能对您有用。但是,就像我说的,它不会真正便携。【参考方案6】:
我同意很多人的观点,尤其是Jason。在 Windows 上,可能会使用 InterlockedAdd 及其朋友。
【讨论】:
【参考方案7】:起初可能会认为本机机器大小的读取和写入是原子的,但有许多问题需要处理,包括处理器/内核之间的缓存一致性。在 Windows 上使用 Interlocked* 等原子操作,在 Linux 上使用等效操作。 C++0x 将有一个“原子”模板来将它们包装在一个漂亮的跨平台界面中。目前,如果您使用平台抽象层,它可能会提供这些功能。 ACE 确实如此,请参阅类模板 ACE_Atomic_Op。
【讨论】:
ACE_Atomic_Op 的文档已经移动 - 现在可以在dre.vanderbilt.edu/~schmidt/DOC_ROOT/ACE/ace/Atomic_Op.inl找到它【参考方案8】:呼应楼上大家所说的,C++0x 之前的语言不能保证任何关于多线程共享内存访问的事情。任何保证都取决于编译器。
【讨论】:
【参考方案9】:除了上面提到的缓存问题...
如果您将代码移植到具有较小寄存器大小的处理器,它将不再是原子的。
IMO,线程问题太棘手,不能冒险。
【讨论】:
【参考方案10】:在 Windows 上,Interlocked***Exchange***Add 保证是原子的。
【讨论】:
【参考方案11】:唯一可移植的方法是为您的编译器使用在 signal.h 头文件中定义的 sig_atomic_t 类型。在大多数 C 和 C++ 实现中,这是一个 int。然后将您的变量声明为“volatile sig_atomic_t”。
【讨论】:
volatile 不会像你认为的那样做***.com/questions/2484980/…sig_atomic_t
对于信号而言是原子的,但对于线程而言不是。从 C++11 开始,从多个线程访问这样的对象是数据竞争和 UB。【参考方案12】:
举个例子
int x;
x++;
x=x+5;
假设第一条语句是原子的,因为它转换为一个占用单个 CPU 周期的 INC 汇编指令。但是,第二个赋值需要几个操作,所以它显然不是原子操作。
另一个例子,
x=5;
同样,您必须反汇编代码才能看到这里到底发生了什么。
【讨论】:
但是编译器可以优化成x+=6
。【参考方案13】:
tc, 我认为当您使用常量(如 6)时,指令不会在一个机器周期内完成。 试试看x+=6的指令集与x++的对比
【讨论】:
【参考方案14】:有些人认为 ++c 是原子的,但要注意生成的程序集。例如使用 'gcc -S' :
movl cpt.1586(%rip), %eax
addl $1, %eax
movl %eax, cpt.1586(%rip)
要增加一个 int,编译器首先将它加载到一个寄存器中,然后将它存储回内存中。这不是原子的。
【讨论】:
如果只有一个线程正在写入变量,这不是问题,因为没有撕裂。【参考方案15】:绝对不! 来自我们最高 C++ 权威 M. Boost 的回答:Operations on "ordinary" variables are not guaranteed to be atomic.
【讨论】:
该链接仅表示arithmetic
操作,它由对“普通”变量的读-更新-写序列组成,不是原子的,无论是 read
还是 write
对“普通”变量的操作是否是原子的。【参考方案16】:
读取和写入是原子的,但您还需要担心编译器会重新排序您的代码。编译器优化可能会违反代码中语句的先发生关系。通过使用 atomic,您不必担心这一点。 ... 原子 i;
soap_status = GOT_RESPONSE ; i = 1
在上面的例子中,变量'i'只有在我们得到一个soap响应后才会被设置为1。
【讨论】:
这不是真的。int
的读写在标准 C++ 中不能保证是原子的,由此产生的数据竞争会导致未定义的行为。以上是关于C++ 对 int 的读写是原子的吗?的主要内容,如果未能解决你的问题,请参考以下文章