我有两个线程,一个更新一个 int,一个读取它。这是一个统计值,其中读取和写入的顺序无关紧要。
我的问题是,我是否需要同步访问这个多字节值?或者,换一种说法,可以部分写入完成并被中断,然后读取发生。
例如,考虑一个值 = 0x0000FFFF,它的增量值为 0x00010000。
有没有我应该担心的值看起来像 0x0001FFFF 的时候?当然,类型越大,发生这种情况的可能性就越大。
我总是同步这些类型的访问,但很好奇社区的想法。
我有两个线程,一个更新一个 int,一个读取它。这是一个统计值,其中读取和写入的顺序无关紧要。
我的问题是,我是否需要同步访问这个多字节值?或者,换一种说法,可以部分写入完成并被中断,然后读取发生。
例如,考虑一个值 = 0x0000FFFF,它的增量值为 0x00010000。
有没有我应该担心的值看起来像 0x0001FFFF 的时候?当然,类型越大,发生这种情况的可能性就越大。
我总是同步这些类型的访问,但很好奇社区的想法。
男孩,什么问题。答案是:
是的,不,嗯,嗯,这取决于
这一切都归结为系统的架构。在 IA32 上,正确对齐的地址将是原子操作。未对齐的写入可能是原子的,它取决于使用的缓存系统。如果内存位于单个 L1 缓存行中,则它是原子的,否则不是。CPU 和 RAM 之间的总线宽度会影响原子性:8086 上正确对齐的 16 位写入是原子性的,而 8088 上的相同写入不是因为 8088 只有 8 位总线,而 8086 有16 位总线。
此外,如果您使用 C/C++,请不要忘记将共享值标记为 volatile,否则优化器会认为该变量永远不会在您的线程之一中更新。
一开始可能会认为本机机器大小的读取和写入是原子的,但有许多问题需要处理,包括处理器/内核之间的缓存一致性。在 Windows 上使用 Interlocked* 等原子操作,在 Linux 上使用等效操作。C++0x 将有一个“原子”模板来将它们包装在一个漂亮的跨平台界面中。目前,如果您使用平台抽象层,它可能会提供这些功能。 ACE确实如此,请参阅类模板ACE_Atomic_Op。
如果您正在读取/写入 4 字节值并且它在内存中是 DWORD 对齐的并且您在 I32 架构上运行,那么读取和写入是原子的。
是的,您需要同步访问。在 C++0x 中,这将是一场数据竞争和未定义的行为。对于 POSIX 线程,它已经是未定义的行为。
在实践中,如果数据类型大于本机字长,您可能会得到错误的值。此外,由于移动读取和/或写入的优化,另一个线程可能永远不会看到写入的值。
您必须同步,但在某些架构上,有一些有效的方法可以做到这一点。
最好是使用子例程(可能隐藏在宏后面),以便您可以有条件地用特定于平台的实现替换实现。
Linux 内核已经有一些这样的代码。
在 Windows 上,Interlocked***Exchange***Add 保证是原子的。
呼应楼上大家所说的,C++0x 之前的语言不能保证任何关于多线程共享内存访问的事情。任何保证都取决于编译器。
不,它们不是(或者至少你不能假设它们是)。话虽如此,有一些技巧可以原子地执行此操作,但它们通常不可移植(请参阅Compare-and-swap)。
我同意许多人的观点,尤其是杰森。在 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,编译器首先将它加载到一个寄存器中,然后将它存储回内存中。这不是原子的。
绝对不!来自我们最高 C++ 权威 M. Boost 的回答:
不能保证对“普通”变量的操作是原子的。
唯一可移植的方法是为您的编译器使用在 signal.h 头文件中定义的 sig_atomic_t 类型。在大多数 C 和 C++ 实现中,这是一个 int。然后将您的变量声明为“volatile sig_atomic_t”。
读取和写入是原子的,但您还需要担心编译器会重新排序您的代码。编译器优化可能会违反代码中语句的先发生关系。通过使用 atomic,您不必担心这一点。... 原子 i;
肥皂状态 = GOT_RESPONSE ;我 = 1
在上面的例子中,变量'i'只有在我们得到一个soap响应后才会被设置为1。