举一个例子,假如有两个全局变量:
int x = 0;
int y = 0;
然后我们在一个线程里执行:
x = 1;
y = 2;
在另一个线程里执行:
if (y == 2) {
x = 3;
y = 4;
}
如果你认为有两种可能,1、2和3、4的话,那说明你是按典型的程序员思维看问题的--没有像编译器和处理器一样处理问题。事实上, 1、4也是一种可能的结果。有两个基本原因造成这一后果:
在多线程对单例进行初始化的过程中,有一个双重检查锁定的技巧,基本实现如下:
class singleton {
public:
static singleton* instance()
{
if (inst_ptr_ == nullptr)
{
std::lock_guard<std::mutex> lk(mutex_);
if (inst_ptr_ == nullptr) {
inst_ptr_ = new singleton();
}
}
return inst_ptr_;
}
private:
singleton() {}
singleton(const singleton&) {}
singleton& operator = (const singleton&);
private:
static singleton* inst_ptr_;
static std::mutex mutex_;
};
singleton* singleton::inst_ptr_ = nullptr;
std::mutex singleton::mutex_;
代码目的是消除大部分执行路径上的加锁开销。意图是:如果 inst_ptr_ 没有被初始化,执行才会进入加锁的路径,防止单例被构造多次;如果 inst_ptr_ 已经被初始化,那它就会被直接返回,不会产生额外开销。这看起来很棒,但直到2000年才有人发现了漏洞,而且在每个语言都发现了,原因是内存读写是乱序的。即创建实例 inst_ptr_ = new singleton(); 是其实分如下三个步骤完成:
上面这三个步骤如果是按顺序进行的,那上面的双重检查锁定的就没有任何问题。但除了确定步骤1首先执行,2和3的顺序是不确定的。假如线程A按1、3、2的顺序执行,当执行完3后,就切到线程B,因为 inst_ptr_ 不为 nullptr 直接 return inst_ptr_ 得到一个对象,而这个对象没有被构造!严重 bug 就出现了!
在C++11中可以用原子操作实现真正线程安全的单例模式,具体实现如下:
class singleton {
public:
static singleton* instance()
{
singleton* ptr = inst_ptr_.load(std::memory_order_acquire);
if (inst_ptr_ == nullptr)
{
std::lock_guard<std::mutex> lk(mutex_);
ptr = inst_ptr_.load(std::memory_order_relaxed);
if (inst_ptr_ == nullptr) {
ptr = new singleton();
inst_ptr_.store(ptr, std::memory_order_release);
}
}
return ptr;
}
private:
singleton() {}
singleton(const singleton&) {}
singleton& operator = (const singleton&);
private:
static std::atomic<singleton*> inst_ptr_;
static std::mutex mutex_;
};
std::atomic<singleton*> singleton::inst_ptr_;
std::mutex singleton::mutex_;
class singleton {
public:
static singleton& instance()
{
static singleton instance_;
return instance_;
}
private:
singleton() {}
singleton(const singleton&) {}
singleton& operator = (const singleton&);
};
Scott Meyers 在《Effective C++》中的提出另一种更优雅的单例模式实现,使用 local static 对象(函数内的 static 对象)。当第一次访问 instance() 方法时才创建实例。C++0x之后该实现是线程安全的,C++0x之前仍需加锁。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。