对int变量赋值的操作是原子的吗?

X86平台上,对int变量赋值的操作是原子的吗?为什么?mips和其他平台上呢?
关注者
395
被浏览
167,522
登录后你可以
不限量看优质回答私信答主深度交流精彩内容一键收藏

首先是:

x86汇编中,对任何内存地址中的1byte的读永远是原子的.也就是说对一个char的读取永远是原子的,对内存地址对齐2byte的int16类型的读取是原子的,对4byte对齐的int32类型读取是原子的,从从奔腾开始,对8byte对齐地址的int64读取是原子的.所以如果你用的是汇编,保证这些就行了.但C/C++中又是另一番情景:

C/C++中,编译器保证基础类型的内存对齐,例如保证double类型的对齐是8(或者4,忘了),

即使是malloc出来的也可以保证对齐.但是由于各种不可避免的指针转换,例如 char a[4],float* p=(float*)a的存在,使得对齐的保证基本名存实亡.而且,当一个比较长的类型,例如double被编译器放入寄存器的时候,C++标准根本不保证只用一条指令就将它放入一个寄存器中.例如我可以先把前半部分放入eax,等一会儿再把后半部分放入edx等等.不过,如果你能够确保对齐,那么大多数情况下虽然UB,但你的代码还是有可能正常工作的.

再然后,其实上面说的根本不用考虑,因为在C/C++标准中,一个变量除了使用atomic相关的函数以外,任何多线程同时进行的读写实际上都是UB.所以,除非使用标准中的atomic功能,或者使用编译器自带的一些扩展,例如InterlockedAdd之类的,否则都是bug的隐患.例如,有非常多的开O2以上优化就出错的多线程相关代码就是由于类似的原因导致的.

一个很经典的例子就是一个网上流传的很广的C++的单例类,以下是那段代码:

class SingleTon{  
public:  
    static SingleTon* getInstance(){  
        if(NULL == instance){  
            EnterCriticalSection(&cs);   
            if(NULL == instance){//双检锁,在进入临界区后再检测一次是否对象已经建立  
                instance = new SingleTon();  
            }  
            LeaveCriticalSection(&cs);    
        }  
        return instance;  
    }  
private: 
    static SingleTon* instance; 
    //........................

这个双检锁的代码很可能不能正常工作,因为首先是编写者没有告知编译器必须假设instance是可能被其他线程改变的,因此编译器完全可以认为两次if只保留一个就行了(当然也可能不会).因此首先instance必须改为volatile的,然后就是上面所说的原子性,instance应该改为atomic<Singleton*>.

C/C++中变量的原子性其实是个巨大的坑,C++11和C11之前对多线程的问题几乎只字不提,也没有语言层面对原子性的保证,(上文中那段单例的代码应该也是C11之前出现的).所以程序员也没有更好的办法,只能使用GCC和VC里自带的那堆原子操作,或者懒了就直接不考虑这问题了.因此只能写这种有隐含问题的代码,现在没问题了,大胆用atomic<>吧.