c++oding="utf-8" ?>
std::atomic 不是真正无锁,因其底层依赖互斥量而非原子指令;它通常超128位且需同步控制块引用计数,is_lock_free() 返回 false。

std::atomic
为什么 std::atomic> 不是真正无锁
调用 is_lock_free() 会返回 false(绝大多数平台如此);因为 shared_ptr 含两个指针(对象指针 + 控制块指针),通常超过单个原子指令能操作的宽度(如 128 位),编译器无法用 cmpxchg16b 或类似指令原子更新全部字段。GCC/Clang 在 x86-64 下实际生成的是带内部互斥量的封装逻辑。
- 运行时可验证:
static_assert(!std::atomic<:shared_ptr>>::is_lock_free()); - Clang 15+ 在启用
-m128bit-cmpxchg且目标平台支持时可能尝试 lock-free,但 libc++ 仍未启用该路径 - 即使硬件支持 128 位原子操作,控制块引用计数的并发修改仍需额外同步,无法规避锁语义
替代方案:用 std::atomic<:uintptr_t> 手动管理裸指针 + 引用计数
真无锁共享指针必须拆解:把对象指针和引用计数分离,用 ABA-safe 的双字原子操作(如 std::atomic<:pair uint32_t>>)或 Hazard Pointer / RCU 等机制保活。简易版可基于 std::atomic + 自定义控制块:
struct atomic_shared_ptr_node {
void* ptr;
std::atomic<uint32_t> refcount;
std::atomic<uint32_t> weakcount;
};
// 原子读:load acquire + 内存序控制
void* load_ptr(std::atomic<:uintptr_t>* p) {
auto v = p->load(std::memory_order_acquire);
return reinterpret_cast<void>(v & ~0x3ULL); // 低两位预留标记位
}
</void></:uintptr_t></uint32_t></uint32_t>
- 必须自己处理
refcount增减、weak count、内存释放时机(不能依赖shared_ptr析构器自动调度) - 写入需 CAS 循环,失败时重试并检查 ABA(例如用版本号或 epoch 计数)
- 不兼容标准
shared_ptr接口,无法直接传给接受std::shared_ptr的函数
何时该坚持用 std::atomic>
多数场景下,它的“伪无锁”已足够快——内部锁粒度极小(仅保护控制块字段),且避免了用户层复杂同步逻辑。适用条件明确:
- 并发读远多于写(例如配置指针、只读服务句柄)
- 不需要严格 lock-free 保证(如硬实时系统、中断上下文)
- 要与现有
shared_ptr生态无缝协作(make_shared、自定义删除器、别名构造等) - 写操作频率低(每秒万级以下),实测性能瓶颈不在原子操作本身,而在对象分配或业务逻辑
容易被忽略的关键点
即使你决定手写无锁版本,也得直面三个硬伤:
-
std::shared_ptr的控制块分配本身不是无锁的——make_shared调用operator new,而 malloc 实现几乎都含锁 - 对象析构发生在任意线程(除非你强制绑定到某线程执行),无法保证 cache locality 和延迟确定性
- 没有标准方式做弱引用等待(
weak_ptr::lock()的原子性依赖控制块锁),自行实现极易引入 use-after-free
真正需要 lock-free 共享语义时,优先考虑 absl::InlinedVector 类型的不可变数据结构 + 原子指针切换,而非模拟 shared_ptr 行为。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











