C++ shared_ptr
多线程使用
一、核心结论
- 引用计数:
shared_ptr
的引用计数操作是原子的,线程安全 - 控制块修改:修改
shared_ptr
指向的对象需要同步 - 被管理对象:若对象本身非线程安全,访问时仍需加锁
二、分场景详解
场景1:多线程读取同一 shared_ptr
#include <memory>
#include <thread>
#include <iostream>
#include <vector>
std::shared_ptr<int> global_ptr = std::make_shared<int>(42);
void reader() {
auto local = global_ptr; // 安全:引用计数原子增加
std::cout << "read: " << *local << "\n";
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(reader);
}
for (auto& t : threads) t.join();
}
输出:
read: read: 42
42
read: 42
read: 42
read: 42
场景2:多线程修改同一 shared_ptr
#include <memory>
#include <thread>
#include <mutex>
#include <iostream>
std::shared_ptr<int> globalPtr;
std::mutex mtx;
void writer(int val) {
std::lock_guard<std::mutex> lock(mtx);
globalPtr = std::make_shared<int>(val); // 必须加锁
}
int main() {
std::thread t1(writer, 100);
std::thread t2(writer, 200);
t1.join();
t2.join();
std::cout << "final value: " << *globalPtr << "\n"; // 200
}
场景3:访问被管理对象
#include <memory>
#include <thread>
#include <mutex>
struct Counter {
int value = 0;
std::mutex mtx;
};
std::shared_ptr<Counter> counter = std::make_shared<Counter>();
void increment() {
std::lock_guard<std::mutex> lock(counter->mtx);
++counter->value;
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(increment);
}
for (auto& t : threads) t.join();
std::cout << "结果: " << counter->value << "\n"; // 10
}
三、错误案例(未正确同步)
#include <memory>
#include <thread>
#include <vector>
std::shared_ptr<int> unsafe_ptr;
void unsafe_writer() {
unsafe_ptr = std::make_shared<int>(42); // 数据竞争
}
void unsafe_reader() {
if (auto local = unsafe_ptr) { // 可能读到中间状态
std::cout << *local << "\n";
}
}
int main() {
std::thread t1(unsafe_writer);
std::thread t2(unsafe_reader);
t1.join();
t2.join();
}
可能结果:
0 // 未初始化值
42 // 正确值
Segmentation fault // 崩溃
四、正确实现(完整示例)
#include <iostream>
#include <memory>
#include <thread>
#include <mutex>
#include <vector>
// 共享资源
struct Data {
int value = 0;
std::mutex mtx;
};
std::shared_ptr<Data> global_data;
std::mutex globalMtx;
void writer(int val) {
// 安全修改指针
auto new_data = std::make_shared<Data>();
{
std::lock_guard<std::mutex> lock(globalMtx);
global_data = new_data;
}
// 安全修改数据
std::lock_guard<std::mutex> data_lock(new_data->mtx);
new_data->value = val;
}
void reader() {
std::shared_ptr<Data> local_ptr;
{
std::lock_guard<std::mutex> lock(globalMtx);
local_ptr = global_data; // 安全获取副本
}
if (local_ptr) {
std::lock_guard<std::mutex> lock(local_ptr->mtx);
std::cout << "read: " << local_ptr->value << "\n";
}
}
int main() {
std::vector<std::thread> writers;
std::vector<std::thread> readers;
// 启动3个写入线程
for (int i = 0; i < 3; ++i) {
writers.emplace_back(writer, i * 100);
}
// 启动5个读取线程
for (int i = 0; i < 5; ++i) {
readers.emplace_back(reader);
}
for (auto& t : writers) t.join();
for (auto& t : readers) t.join();
return 0;
}
典型输出:
读取: 200
读取: 200
读取: 200
读取: 200
读取: 200
五、编译与运行
编译命令:
g++ -std=c++17 -pthread -o shared_ptr_thread shared_ptr_thread.cpp
运行结果:
$ ./shared_ptr_thread 读取: 200 读取: 200
六、线程安全总结
操作类型 | 是否需要锁 | 说明 |
---|---|---|
拷贝/析构 shared_ptr |
否 | 引用计数操作是原子的 |
修改 shared_ptr 指向 |
是 | 修改指向的对象需要同步 |
访问被管理对象 | 取决于对象 | 若对象非线程安全则需锁 |
同时读写不同 shared_ptr |
否 | 不同实例互不影响 |
七、最佳实践
- 最小化共享:尽量让每个线程持有自己的
shared_ptr
副本 - 分层加锁:
- 第一层锁保护
shared_ptr
的修改 - 第二层锁保护被管理对象的访问
- 第一层锁保护
- 使用
weak_ptr
打破循环:std::shared_ptr<Node> parent; std::weak_ptr<Node> child; // 避免循环引用