CPU动态调换执行指令

    以前,我从某些书籍上有看到编译器在优化代码的时候会改变C++代码的执行顺序;其实CPU为了优化执行的效率也可能会动态改变代码执行顺序。

以下内容来自《程序员的自我修养--链接、装载与库》

一段典型的double-check的singleton代码如下:

volatile T* pInst = 0; 
T* GetInstance() 
{ 
    if (pInst == NULL) 
    { 
        lock(); 
        if (pInst == NULL) 
            pInst = new T(); 
        unlock();    
    } 
    return pInst; 
}

    抛开逻辑,这样的代码是没有问题的,当函数返回时,pInst总是指向一个有效对象。而lock和unlock防止多线程竞争导致的麻烦。

但是实际上这样的代码是有问题的。问题的来源任然是CPU的乱序执行。C++里的new其实包含了两个步骤:

(1)分配内存。

(2)调用构造函数。

所以pInst = new T()包含了三个步骤:

(1)分配内存。

(2)在内存位置上调用构造函数。

(3)将内存地址赋值给pInst。

    在这三步中,(2)和(3)的顺序是可以颠倒的。也就是说,完全有可能出现这样的情况:pInst的值已经不是NULL,但对象任然没有构造完毕。这时候如果出现另外一个对GetInstance的并发调用,此时第一个if内的表达式pInst==NULL为false,所以这个调用会直接返回尚未构造完全的对象的地址(pInst)以提供给用户使用。

    为此,阻止CPU换序是必需的。遗憾的是,现在并不存在可移植的阻止换序的方法。通常情况下是调用CPU提供的一条指令,这条指令常常被称为barrier。一条barrier指令会阻止CPU将该指令之前的指令交换到barrier之后,反之亦然。换句话说,barrier指令的作用类似于一个拦水坝,阻止换序“穿透”这个大坝。

    许多体系结构的CPU都提供barrier指令,不过它们的名称各不相同,例如POWERPC提供的其中一条指令名叫lwsync。我们可以这样来保证线程安全:

#define barrier() __asm__ volaticle("lwsync") 
volatile T* pInst = 0; 
T* GetInstance() 
{ 
    if (pInst == NULL) 
    { 
        lock(); 
        if (pInst == NULL) 
        { 
            T* tmp = new T(); 
            barrier(); 
            pInst = tmp; 
        }    
        unlock();    
    } 
    return pInst; 
}

    由于barrier的存在,对象的构造一定在barrier执行之前完成,因此当pInst被赋值时,对象总是完好的。

posted @ 2012-10-28 08:21  opencoder  阅读(543)  评论(0编辑  收藏  举报