什么时候可以保证64位写入是原子的,在基于Intel x86的平台上用C编程时(特别是使用英特尔编译器运行MacOSX 10.4的基于Intel的Mac)?例如:
unsigned long long int y; y = 0xfedcba87654321ULL; /* ... a bunch of other time-consuming stuff happens... */ y = 0x12345678abcdefULL;
如果另一个线程在y的第一次赋值完成后检查y的值,我想确保它看到值0xfedcba87654321或值0x12345678abcdef,而不是它们的某些混合.我想这样做没有任何锁定,如果可能的话没有任何额外的代码.我希望在使用64位编译器(64位Intel编译器)时,在能够支持64位代码的操作系统(MacOSX 10.4)上,这些64位写入将是原子的.这总是如此吗?
最好的办法是避免尝试用原语构建自己的系统,而是使用锁定,除非它在分析时真的显示为热点.(如果你认为你可以聪明并且避免锁定,那就不要.你不是.那是包括我和其他人在内的一般"你".)你应该至少使用自旋锁,参见spinlock(3).无论你做什么,都不要试图实现"你自己的"锁.你会弄错的.
最终,您需要使用操作系统提供的任何锁定或原子操作.在所有情况下完全正确地获取这些东西是非常困难的.通常它可能涉及特定处理器的特定版本的勘误表之类的知识.("哦,该处理器的2.0版本没有在正确的时间执行缓存一致性窥探,它在版本2.0.1中已修复,但在2.0版本中需要插入一个.")只需在C中的变量上打一个关键字几乎总是不够.NOP
volatile
在Mac OS X上,这意味着您需要使用atomic(3)中列出的函数对32位,64位和指针大小的数量执行真正的原子跨所有CPU操作.(使用后者对指针进行任何原子操作,这样你就可以自动进行32/64位兼容.)无论你是想做原子比较和交换,递增/递减,自旋锁定还是堆栈/队列,都会这样做.管理.幸运的是,spinlock(3),atomic(3)和barrier(3)函数应该在Mac OS X支持的所有CPU上都能正常工作.
在x86_64上,Intel编译器和gcc都支持一些内部原子操作函数.这是gcc的文档:http://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Atomic-Builtins.html
英特尔编译器文档也在这里讨论它们:http://softwarecommunity.intel.com/isn/downloads/softwareproducts/pdfs/347603.pdf(第164页左右).
根据英特尔处理器手册的第3A部分 - 系统编程指南第7章,如果在64位边界,Pentium或更高版本上对齐,并且未对齐(如果仍在高速缓存行内),则将以原子方式执行四字访问. P6或更新.您应该使用以确保编译器不会尝试将写入缓存在变量中,并且您可能需要使用内存栅栏例程来确保以正确的顺序进行写入.volatile
如果需要将写入现有值的值作为基础,则应使用操作系统的Interlocked功能(例如,Windows具有InterlockedIncrement64).
在Intel MacOSX上,您可以使用内置系统原子操作.没有为32位或64位整数提供原子获取或设置,但您可以使用提供的CompareAndSwap构建它.您可能希望在XCode文档中搜索各种OSAtomic功能.我在下面写了64位版本.32位版本可以使用类似命名的函数完成.
#include// bool OSAtomicCompareAndSwap64Barrier(int64_t oldValue, int64_t newValue, int64_t *theValue); void AtomicSet(uint64_t *target, uint64_t new_value) { while (true) { uint64_t old_value = *target; if (OSAtomicCompareAndSwap64Barrier(old_value, new_value, target)) return; } } uint64_t AtomicGet(uint64_t *target) { while (true) { int64 value = *target; if (OSAtomicCompareAndSwap64Barrier(value, value, target)) return value; } }
请注意,Apple的OSAtomicCompareAndSwap函数以原子方式执行操作:
if (*theValue != oldValue) return false; *theValue = newValue; return true;
我们在上面的例子中使用它来创建一个Set方法,首先获取旧值,然后尝试交换目标内存的值.如果交换成功,则表示内存的值仍然是交换时的旧值,并且在交换期间给它新值(它本身是原子的),所以我们完成了.如果它没有成功,那么其他一些线程通过在我们抓住它和我们试图重置它时修改其间的值来干扰.如果发生这种情况,我们可以简单地循环并再次尝试,只有最小的惩罚.
Get方法背后的想法是我们可以先获取值(如果另一个线程正在干扰,则可能是也可能不是实际值).然后我们可以尝试将值与自身交换,只需检查初始抓取是否等于原子值.
我没有对我的编译器进行检查,所以请原谅任何错别字.
您特别提到了OSX,但是如果您需要在其他平台上工作,Windows有许多Interlocked*功能,您可以在MSDN文档中搜索它们.其中一些适用于Windows 2000 Pro及更高版本,而一些(特别是一些64位功能)是Vista的新功能.在其他平台上,GCC 4.1及更高版本具有各种__sync*函数,例如__sync_fetch_and_add().对于其他系统,您可能需要使用程序集,您可以在src/system/libroot/os/arch中的HaikuOS项目的SVN浏览器中找到一些实现.
在X86上,原子地写入对齐的64位值的最快方法是使用FISTP.对于未对齐的值,您需要使用CAS2(_InterlockedExchange64).由于BUSLOCK,CAS2操作非常慢,因此通常可以更快地检查对齐并为对齐的地址执行FISTP版本.实际上,这就是Intel Threaded构建模块实现Atomic 64位写入的方式.