这个双重检查锁定修复有什么问题?

Jos*_*vin 5 singleton multithreading locking sequence-points double-checked-locking

所以我看到很多文章现在声称在C++上双重检查锁定,通常用于防止多个线程尝试初始化一个懒惰的单例,被打破了.正常双重检查锁定代码如下所示:

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;

public:
    static singleton & instance()
    {
        static singleton* instance;

        if(!instance)
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;
        }

        return *instance;
    }
};
Run Code Online (Sandbox Code Playgroud)

问题显然是行分配实例 - 编译器可以自由分配对象,然后将指针分配给它,或者将指针设置为它将被分配的位置,然后分配它.后一种情况打破了这个习惯用法 - 一个线程可以分配内存并分配指针,但在它进入休眠状态之前不运行单例的构造函数 - 然后第二个线程将看到该实例不为null并尝试返回它,即使它尚未建成.

看到了一个使用线程局部布尔值的建议并检查而不是instance.像这样的东西:

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;
    static boost::thread_specific_ptr<int> _sync_check;

public:
    static singleton & instance()
    {
        static singleton* instance;

        if(!_sync_check.get())
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;

            // Any non-null value would work, we're really just using it as a
            // thread specific bool.
            _sync_check = reinterpret_cast<int*>(1);
        }

        return *instance;
    }
};
Run Code Online (Sandbox Code Playgroud)

这样每个线程最终都会检查实例是否已创建一次,但在此之后停止,这会带来一些性能损失,但仍然不会像锁定每个调用那么糟糕.但是,如果我们只使用本地静态bool会怎么样?:

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;

public:
    static singleton & instance()
    {
        static bool sync_check = false;
        static singleton* instance;

        if(!sync_check)
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;

            sync_check = true;
        }

        return *instance;
    }
};
Run Code Online (Sandbox Code Playgroud)

为什么这不起作用?即使sync_check在一个线程被另一个线程分配时被读取,垃圾值仍然是非零的,因此是真的.Dobb博士的文章声称你必须锁定,因为你永远不会在重新排序指令的情况下赢得与编译器的争斗.这让我觉得这不能出于某种原因,但我无法弄清楚为什么.如果对序列点的要求与Dobb博士的文章让我相信的那样失败,我不明白为什么锁之后的任何代码都无法重新排序到锁之前.哪会让C++多线程破碎.

我想我可以看到允许编译器专门将sync_check重新排序到锁定之前,因为它是一个局部变量(即使它是静态的,我们也没有返回它的引用或指针) - 但是这仍然可以解决通过使其成为静态成员(实际上是全局的).

那么这项工作还是不会呢?为什么?

coo*_*bez 5

您的修复程序无法解决任何问题,因为对sync_check和instance的写入可以在CPU上无序执行.作为一个例子,假设实例的前两次调用几乎同时发生在两个不同的CPU上.第一个线程将获取锁,初始化指针并按顺序将sync_check设置为true,但处理器可能会更改写入内存的顺序.在另一个CPU上,第二个线程可以检查sync_check,看它是否为真,但实例可能尚未写入内存.有关详细信息,请参阅Xbox 360和Microsoft Windows的无锁编程注意事项.

您提到的特定于线程的sync_check解决方案应该可以正常工作(假设您将指针初始化为0).