引言
std::shared_ptr` 是 C++ 中最常用的智能指针之一,但其线程安全语义常常被误解。本文从引用计数的原子操作出发,详细分析 `shared_ptr` 哪些操作是线程安全的、哪些不是,并给出正确的多线程使用模式,帮助你避免数据竞争和未定义行为。适用于 C++11 及以上标准。
1.shared_ptr 的线程安全:
“部分安全”原则`,shared_ptr` 的线程安全可以总结为以下三条核心规则:
1.1 引用计数的操作是线程安全的
拷贝构造、拷贝赋值、析构。对引用计数的修改(递增/递减)使用原子操作,多个线程同时操作**不同的 `shared_ptr` 对象**(即使它们指向同一资源)是安全的。
底层保证。控制块中的 `ref_count` 和 `weak_count` 均为原子类型,所有修改操作通过`fetch_add`/`fetch_sub` 等原子原语完成。
1.2 指向的资源本身不是线程安全的
`shared_ptr` 只管理内存生命周期,**不提供任何对资源内容的同步保护**。
- 多线程同时读写所管理的对象需要额外的同步(如 `std::mutex`、`std::atomic` 或读写锁)
1.3 同一个 shared_ptr 对象被多线程同时修改是不安全的
- 如果有多个线程对**同一个** `shared_ptr` 对象调用**非 const 成员函数**(如 `reset()`、`operator=`、`swap()`),将产生数据竞争,即使这些操作涉及引用计数修改,也不能保证线程安全。
```cpp
std::shared_ptr<int> global_sp = std::make_shared<int>(42);
// 线程 A
global_sp = std::make_shared<int>(100); // 写操作
// 线程 B
auto sp2 = global_sp; // 读操作
// 数据竞争!同一对象同时被读写
```
> **结论**:可以把 `shared_ptr` 视为一个“小的资源句柄”,它的引用计数部分是线程安全的,但句柄本身的赋值(即改变它指向哪个控制块)不是线程安全的。
---
2.典型实现原理与引用计数
2.1 控制块与内存布局
`shared_ptr` 通常包含两个指针:一个指向所管理的对象,另一个指向**控制块**(control block)