智能指针多线程安全问题学习
1.安全性
在多线程环境下对同一个shared_ptr对象读操作没有问题,它的引用计数是原子的,安全且无锁,但是如果是多线程下有读写操作,以及对shared_ptr指向的对象有读写操作,那么就会发生竞争。shared_ptr多线程问题的本质是它所指向的对象的引用计数是否会因为多线程环境而出错,后一种情况就相当于普通指针,或认为是int写操作。
2.讨论
2.1 多线程独写所指向的对象
转自:https://www.cnblogs.com/gqtcgq/p/7492772.html
shared_ptr<long> global_instance = make_shared<long>(0); std::mutex g_i_mutex; void thread_fcn() { //std::lock_guard<std::mutex> lock(g_i_mutex);//加锁之后结果正确 //shared_ptr<long> local = global_instance; for(int i = 0; i < 100000000; i++) { *global_instance = *global_instance + 1; //*local = *local + 1;//就算使用这个也会出错,因为指向的是同一个global_ins } } int main(int argc, char** argv) { thread thread1(thread_fcn); thread thread2(thread_fcn); thread1.join(); thread2.join(); cout << "*global_instance is " << *global_instance << endl; return 0; }
这就是常见的非线性安全的场景。
2.2 多线程独写shared_ptr对象
转自:http://blog.csdn.net/solstice/article/details/8547547
虽然shared_ptr的引用计数是原子安全的,但是它有两个数据成员,指针和引用计数,对它们一同的写不能原子化。这里其实还是回到了它的本质,所指向对象的引用计数是否会因为多线程环境出现安全问题。
3.线程安全实现
转自chatgpt。
std::shared_ptr
在C++标准库中的实现提供了一定程度的线程安全性,主要体现在引用计数的操作上。具体来说,std::shared_ptr
保证了以下几点的线程安全:
- 引用计数的原子性:引用计数的增加和减少是原子的,这意味着这些操作是线程安全的,可以在多个线程中同时执行,而不会导致数据竞争。
- 拷贝和赋值操作的线程安全:两个不同的
std::shared_ptr
实例可以在不同的线程中安全地拷贝、赋值和销毁。
引用计数部分:std::shared_ptr
内部使用一个控制块来管理引用计数。这个控制块包含两个计数器,一个是共享引用计数,另一个是弱引用计数。以下是一个简化的控制块实现:
class ControlBlock { public: ControlBlock() : shared_count(1), weak_count(1) {} void add_shared() { shared_count.fetch_add(1, std::memory_order_relaxed); } void release_shared() { if (shared_count.fetch_sub(1, std::memory_order_acq_rel) == 1) { delete ptr; // 销毁托管对象 release_weak(); } } void add_weak() { // 这块看不懂是什么意思 weak_count.fetch_add(1, std::memory_order_relaxed); } void release_weak() { if (weak_count.fetch_sub(1, std::memory_order_acq_rel) == 1) { delete this; // 销毁控制块 } } private: std::atomic<int> shared_count; // 保证引用计数是原子的 std::atomic<int> weak_count; void* ptr; // 指向被托管对象的指针 };
如果多个线程仅对 std::shared_ptr
进行读操作(即不会修改共享对象的内容),这些操作是线程安全的。然而,如果有写操作(即修改共享对象的内容),则需要额外的同步机制(如互斥锁)来保护这些操作。
4.拷贝构造函数实现
转自chatgpt。
包括控制块(control block)的使用以及引用计数(reference counting)的管理。
当你拷贝一个 shared_ptr
时,新的 shared_ptr
实例将共享原始 shared_ptr
的控制块,从而确保引用计数正确维护。具体步骤如下:
- 复制指针:新
shared_ptr
将持有与原始shared_ptr
相同的指向被管理对象的指针。 - 复制控制块:新
shared_ptr
将持有与原始shared_ptr
相同的控制块指针。 - 增加引用计数:新
shared_ptr
增加控制块中的引用计数。
template <typename T> class shared_ptr { public: // 拷贝构造函数 shared_ptr(const shared_ptr& other) noexcept : ptr(other.ptr), control_block(other.control_block) {// 指针直接初始化,指向同一个位置 if (control_block) { control_block->add_ref(); // 增加引用计数 } } // 其他构造函数、赋值操作符、析构函数等 private: T* ptr; // 指向被管理对象的指针 ControlBlock* control_block; // 指向控制块的指针 // 控制块定义 struct ControlBlock { int ref_count; // 引用计数 int weak_count; // 弱引用计数 void add_ref() { ++ref_count; } void release_ref() { if (--ref_count == 0) { delete ptr; if (weak_count == 0) { delete this; } } } void add_weak_ref() { ++weak_count; } void release_weak_ref() { if (--weak_count == 0 && ref_count == 0) { delete this; } } }; };