原子操作为何仍需锁?基于ResourcePool单例代码的技术疑问
单例模式代码分析
示例代码
class ResourcePool{ public: ... static inline ResourcePool* singleton() { ResourcePool* p = _singleton.load(butil::memory_order_consume); if (p) { return p; } pthread_mutex_lock(&_singleton_mutex); p = _singleton.load(butil::memory_order_consume); if (!p) { p = new ResourcePool(); _singleton.store(p, butil::memory_order_release); } pthread_mutex_unlock(&_singleton_mutex); return p; } private: .... static butil::static_atomic<ResourcePool*> _singleton; static pthread_mutex_t _singleton_mutex; };
说明:
Butil::memory_order_consume是封装的boostmemory_order_consume。
问题解答
问题一:在多核多线程环境中,首次执行butil::memory_order_consume加载操作,是否能确保指令顺序,防止其他线程在获取p指针前对其进行解引用?
memory_order_consume的核心是维护数据依赖关系的内存序:当线程通过该语义加载到非空的p指针后,所有依赖于p的指令(比如解引用p访问对象成员),绝对不会被编译器或CPU重排到加载p的操作之前。
结合代码里的store用了memory_order_release,两者能建立依赖同步:store之前的ResourcePool初始化操作,对通过consume加载到p的线程完全可见,且解引用p的操作必然是在拿到有效指针之后执行的,不会出现“提前解引用无效指针”的问题。
简单说:能确保依赖于p的解引用操作不会提前到加载p之前,避免无效指针访问的风险。
问题二:若ResourcePool实例已存在则直接返回,不存在则创建并执行_singleton.store(p, butil::memory_order_release),该操作本应通知其他线程,只需其他线程通过butil::memory_order_acquire加载即可,为何还要添加锁?若加锁是为了保证p=_singleton.load(butil::memory_order_consume)的唯一访问,那后续的store操作又有什么作用?
这是标准的**双重检查锁定(DCLP)**实现,锁和原子store的作用完全不同:
锁的核心作用:避免多线程同时创建实例
如果没有锁,当多个线程同时检测到_singleton为空时,都会进入创建实例的分支,导致多个ResourcePool实例被构造,直接破坏单例的唯一性。锁的存在保证同一时间只有一个线程能进入“创建实例”的临界区,确保实例只会被初始化一次。store操作的作用:保证内存可见性和初始化顺序
锁解决了实例唯一性问题,但线程之间的缓存一致性需要靠原子操作的内存序来保证:- 用
release语义执行store,能确保new ResourcePool()的所有初始化代码,都在store完成前执行完毕; - 后续其他线程通过
consume/acquire加载_singleton时,不仅能看到最新的指针值,还能看到指针指向的对象已经完成初始化的状态,不会出现“拿到指针但对象未初始化”的情况。
- 用
另外,第一次的load(consume)是快速路径优化:当实例已经存在时,线程不用加锁就能直接获取指针,避免了每次调用都加锁的性能损耗。
内容的提问来源于stack exchange,提问作者Marsontao_
相关产品推荐
相关产品推荐

