C++ `shared_ptr` 多线程使用

发布于:2025-04-19 ⋅ 阅读:(61) ⋅ 点赞:(0)

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

五、编译与运行
  1. 编译命令

    g++ -std=c++17 -pthread -o shared_ptr_thread shared_ptr_thread.cpp
    
  2. 运行结果

    $ ./shared_ptr_thread
    读取: 200
    读取: 200
    

六、线程安全总结
操作类型 是否需要锁 说明
拷贝/析构 shared_ptr 引用计数操作是原子的
修改 shared_ptr 指向 修改指向的对象需要同步
访问被管理对象 取决于对象 若对象非线程安全则需锁
同时读写不同 shared_ptr 不同实例互不影响

七、最佳实践
  1. 最小化共享:尽量让每个线程持有自己的 shared_ptr 副本
  2. 分层加锁
    • 第一层锁保护 shared_ptr 的修改
    • 第二层锁保护被管理对象的访问
  3. 使用 weak_ptr 打破循环
    std::shared_ptr<Node> parent;
    std::weak_ptr<Node> child; // 避免循环引用
    

网站公告

今日签到

点亮在社区的每一天
去签到