如果我有一个多线程程序,通过引用读取缓存类型的内存。主线程可以在不影响其他线程读取意外值的情况下更改该指针吗?
在我看来,如果更改是原子性的,其他线程将读取旧值或新值;永远不会读取随机内存(或空指针),对吗?
我知道我应该始终使用同步方法,但我还是很好奇。
指针变化是否是原子性的?
更新:我的平台是64位Linux(2.6.29),虽然我也想要跨平台的答案:)
如果我有一个多线程程序,通过引用读取缓存类型的内存。主线程可以在不影响其他线程读取意外值的情况下更改该指针吗?
在我看来,如果更改是原子性的,其他线程将读取旧值或新值;永远不会读取随机内存(或空指针),对吗?
我知道我应该始终使用同步方法,但我还是很好奇。
指针变化是否是原子性的?
更新:我的平台是64位Linux(2.6.29),虽然我也想要跨平台的答案:)
正如其他人所提到的,C语言中没有任何保证,这取决于你的平台。
在大多数现代桌面平台上,对一个字大小的、对齐的位置进行读/写将是原子性的。但这并不能解决你的问题,因为处理器和编译器会重新排序读取和写入的操作。
例如,以下代码是错误的:
线程A:
DoWork();
workDone = 1;
线程 B:
while(workDone != 0);
ReceiveResultsOfWork();
尽管对workDone
的写入是原子性的,但在许多系统上,处理器不能保证在DoWork()
通过写入可见之前,其他处理器将写入workDone
。编译器也可能自由地将对workDone
的写入重新排序到DoWork()
调用之前。在这两种情况下,ReceiveResultsOfWork()
可能会开始使用不完整的数据。C语言对于任何操作是否具有原子性并没有明确规定。我曾经使用过8位总线和16位指针的微控制器,这些系统中的任何指针操作都可能是非原子的。我记得 Intel 386(其中一些有16位总线)也引起了类似的担忧。同样,我可以想象一些具有64位CPU但只有32位数据总线的系统,这可能会涉及到指针操作的非原子性问题。(我还没有检查是否有这样的系统存在。)
编辑:Michael's answer 值得一读。总线大小与指针大小并不是关于原子性的唯一考虑因素;这只是我想到的第一个反例。
指针变化是否保证原子性?
这个区别很重要,因为不同的C/C++实现在这个行为上可能会有所不同。一个特定的平台可以保证原子赋值并仍符合标准。
至于在C/C++中是否保证了整体的原子性,答案是否定的。C标准没有作出任何这方面的保证。保证指针赋值是原子的唯一方法是使用特定于平台的机制来保证赋值的原子性。例如,Win32中的Interlocked方法将提供这种保证。
你在哪个平台上工作?
没错,C语言规范并没有要求指针赋值必须是原子操作,因此你不能保证指针赋值的原子性。
实际情况则取决于你使用的平台、编译器以及可能是你编写程序当天星座的影响。
'normal'指针修改不能保证是原子操作。
检查“比较并交换”(CAS)和其他原子操作,这不是C标准,但大多数编译器都可以访问处理器原语。在GNU gcc的情况下,有几个内置函数可用。
标准保证的唯一类型是sig_atomic_t。
正如其他答案所示,当针对通用x86架构时,它很可能是可以的,但在更多“专业”硬件上非常危险。
如果你真的很想知道,你可以将sizeof(sig_atomic_t)与sizeof(int*)进行比较,并查看它们在目标系统上的大小。
这其实是一个相当复杂的问题。我曾经问过一个类似的问题,并阅读了所有指向我的内容。我学到了很多关于现代架构中缓存如何工作的知识,但没有找到什么是最终确定的答案。正如其他人所说,如果总线宽度小于指针位宽,你可能会遇到麻烦。特别是如果数据跨越了缓存行边界。
一个谨慎的架构将使用锁。