基于引用计数的config单例线程安全吗?C++17下如何解决潜在问题?
这段"自动释放型"单例代码的线程安全性分析及C++17解决方案
一、原代码的线程安全问题
这段代码不是线程安全的,核心问题出在静态std::weak_ptr<config> p的并发访问上:
std::weak_ptr和std::shared_ptr的线程安全仅局限于引用计数的原子操作,以及持有合法shared_ptr时对底层对象的访问。但智能指针对象本身的读写(比如修改p的值、判断!p、调用p.lock())属于普通的非原子操作,多线程并发执行这些操作会触发数据竞争,属于C++标准中的未定义行为。- 具体竞态场景:
- 当
p为空时,两个线程同时进入第一个分支,都会创建新的config实例并赋值给p,导致先创建的实例被直接覆盖,引用计数异常,甚至可能出现实例被提前销毁的问题。 - 当
p指向的实例刚好被最后一个shared_ptr释放(即p过期)时,多个线程可能同时进入else分支,重复创建实例并赋值给p,同样会导致不必要的实例创建和数据竞争。
- 当
二、C++17下的解决方案
由于C20才引入std::atomic<std::weak_ptr<T>>来原生支持weak_ptr的原子操作,C17中需要手动添加同步机制来保护p的访问,最直接且可靠的方式是使用互斥锁:
修改后的线程安全代码
#include <memory> #include <mutex> class config {}; std::shared_ptr<config> get_config() { static std::weak_ptr<config> p; static std::mutex mtx; // 用锁包裹所有对p的读写操作,保证同一时间只有一个线程能访问p std::lock_guard<std::mutex> lock(mtx); if (!p) { return p = std::make_shared<config>(); } else if (auto instance = p.lock()) { return instance; } else { return p = std::make_shared<config>(); } }
方案说明
std::mutex保证了对p的所有操作(判断空、lock、赋值)都是互斥的,彻底消除了数据竞争。std::lock_guard会自动管理锁的生命周期,避免手动解锁时出现遗漏或异常安全问题。- 作为临时workaround,这种实现方式足够简洁可靠,不需要修改现有调用逻辑,符合你的场景需求。
内容的提问来源于stack exchange,提问作者Dominik Kaszewski
相关产品推荐
相关产品推荐

