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重新排序到锁定之前,因为它是一个局部变量(即使它是静态的,我们也没有返回它的引用或指针) - 但是这仍然可以解决通过使其成为静态成员(实际上是全局的).
那么这项工作还是不会呢?为什么?
您的修复程序无法解决任何问题,因为对sync_check和instance的写入可以在CPU上无序执行.作为一个例子,假设实例的前两次调用几乎同时发生在两个不同的CPU上.第一个线程将获取锁,初始化指针并按顺序将sync_check设置为true,但处理器可能会更改写入内存的顺序.在另一个CPU上,第二个线程可以检查sync_check,看它是否为真,但实例可能尚未写入内存.有关详细信息,请参阅Xbox 360和Microsoft Windows的无锁编程注意事项.
您提到的特定于线程的sync_check解决方案应该可以正常工作(假设您将指针初始化为0).
| 归档时间: |
|
| 查看次数: |
2210 次 |
| 最近记录: |