2020年3月17日
即使加锁,也不一定唯一初始化
在多线程可能对同一个单件进行初始化的情况下,有一个双重检查锁定的技巧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class singleton { public: static singleton* instance(); … private: static singleton* inst_ptr_; };
singleton* singleton::inst_ptr_ = nullptr;
singleton* singleton::instance() { if (inst_ptr_ == nullptr) { lock_guard lock; if (inst_ptr_ == nullptr) { inst_ptr_ = new singleton(); } } return inst_ptr_; }
|
- 原本的意图是:
- 如果 inst_ptr_ 没有被初始化,执行才会进入加锁的路径,防止单件被构造多次
- 这里面有两个需求:
- 单一初始化inst_ptr_。方法:写成下面即可
1 2 3 4 5 6 7 8
| singleton* singleton::instance() { lock_guard lock; if (inst_ptr_ == nullptr) { inst_ptr_ = new singleton(); } return inst_ptr_; }
|
- 为什么还要再加一个
if (inst_ptr_ == nullptr)
- 效率的角度,别直接上来就锁。如果inst_ptr_已被初始化,锁就没必要加
- 产生问题的本质原因:
- 优化编译器会努力击败你试图想防止优化的努力
- 多处理器会以令人意外的方式让代码走到错误的执行路径上去。
修正方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class singleton { public: static singleton* instance(); … private: static mutex lock_; static atomic<singleton*> inst_ptr_; };
mutex singleton::lock_; atomic<singleton*> singleton::inst_ptr_;
singleton* singleton::instance() { singleton* ptr = inst_ptr_.load( memory_order_acquire); if (ptr == nullptr) { lock_guard<mutex> guard{lock_}; ptr = inst_ptr_.load( memory_order_relaxed); if (ptr == nullptr) { ptr = new singleton(); inst_ptr_.store( ptr, memory_order_release); } } return inst_ptr_; }
|