关于 智能指针 的线程安全问题

news/2024/7/6 1:33:10

先说结论,智能指针都是非线程安全的。

多线程调度智能指针

这里案例使用的是shared_ptr,其他的unique_ptr或者weak_ptr的结果都是类似的,如下多线程调度代码:

#include <memory>
#include <thread>
#include <vector>
#include <atomic>
#include <mutex>
#include <unistd.h>
#include <iostream>#include <iostream>using namespace std;class PTR{
public:PTR(){}int GetData() {return data_;}void SetData(int a) {data_.store(a, std::memory_order_relaxed);}void DoSomething() {for (int i = 0; i< 10; i++) {data_.fetch_add(1, std::memory_order_relaxed);}}
private:std::atomic<int> data_;
};std::shared_ptr<PTR> ptr;
std::mutex mu;void ThreadFunc(int num) {if (!ptr) {ptr.reset(new PTR());ptr->SetData(2);}ptr->DoSomething();std::cout << "thread " << num << " GetData " << ptr->GetData() << " ref_count: " << ptr.use_count() << std::endl;ptr.reset();
}int main(int args, char* argv[]) {int threads = atoi(argv[1]);std::vector<std::thread> thread_vec;for (int i = 0; i < threads; i++) {thread_vec.emplace_back(std::thread(ThreadFunc, i));}for (auto& a : thread_vec) {a.detach();}return 0;
}

大体逻辑是多个线程访问shared_ptr<PTR> ptr,每次执行之前如果发现这个ptr是空的,则会先初始化一下,再做一些累加逻辑,处理完成之后再设置为空。
因为ThreadFunc中没有同步机制,我们多线程下可能的执行行为如下:
在这里插入图片描述
其中t1 < t2 < t3 <t4,也就是有可能thread1 在t3时刻 reset了一个空的ptr, 但是在t4时刻,thread2根据自己之前t2时判断的ptr不为空的逻辑,直接使用了空的ptr去访问数据,从而造成BAD_ACCESS问题。

因为我们在ThreadFunc内修改 一个被全局共享的ptr,所以,这个时候我们可能想要知道shared_ptr在被修改的时候内部行为是什么样子的。

shared_ptr 的实现

其他的智能指针通过reset构造对象的逻辑大体相似。

shared_ptr实现其实很简单,这里主要还是看一下它的reset逻辑,即 使用一个新的对象初始化当前shared_ptr 引用的对象

_SafeConv<_Yp>
reset(_Yp* __p) // _Yp must be complete.
{// Catch self-reset errors.__glibcxx_assert(__p == 0 || __p != _M_ptr);// 将__p 构造为shared_ptr,并且与当前shared_ptr进行地址以及引用计数的交换__shared_ptr(__p).swap(*this);
}// 直接交换两个对象的地址,再交换shared_ptr的引用计数
void
swap(__shared_ptr<_Tp, _Lp>& __other) noexcept
{std::swap(_M_ptr, __other._M_ptr);_M_refcount._M_swap(__other._M_refcount);
}void
_M_swap(__shared_count& __r) noexcept
{_Sp_counted_base<_Lp>* __tmp = __r._M_pi;__r._M_pi = _M_pi;_M_pi = __tmp;
}

所以,这个过程本身就不是原子的,再加上外部同一个线程函数内部多次修改全局shared_ptr的地址,线程安全问题显而易见。
而当我们通过operator->() 去访问shared_ptr 的时候则是没有任何问题的,多线程下只要不修改,任何的读都是ok的。

element_type*
operator->() const noexcept
{
_GLIBCXX_DEBUG_PEDASSERT(_M_get() != nullptr);
return _M_get();
}element_type*
_M_get() const noexcept
{ return static_cast<const __shared_ptr<_Tp, _Lp>*>(this)->get(); }
};/// Return the stored pointer.
element_type*
get() const noexcept
{ return _M_ptr; }

综上,大家在多线程内部使用共享的智能指针的时候需要减少对智能指针的修改,或者修改的时候加上锁同步,防止出现智能指针内部的不同步行为,对于ThreadFunc来说,修改以及访问的逻辑需要有锁的介入才行:

void ThreadFunc(int num) {mu.lock();if (!ptr) {ptr.reset(new PTR());ptr->SetData(2);}ptr->DoSomething();std::cout << "thread " << num << " GetData " << ptr->GetData() << " ref_count: " << ptr.use_count() << std::endl;ptr.reset();mu.unlock();
}

http://lihuaxi.xjx100.cn/news/219011.html

相关文章

C++中 public,protected, private 访问标号小结

第一&#xff1a;private, public, protected 访问标号的访问范围。 private&#xff1a; 只能由1.该类中的函数、2.其友元函数访问。 不能被任何其他访问&#xff0c;该类的对象也不能访问。 protected&#xff1a; 可以被1.该类中的函数、2.子类的函数、以及3.其友元函数…

做片子留着备用 超级游戏影视配乐音效库36套合集

做片子留着备用 超级游戏影视配乐音效库36套合集 Epic Stock Media 创造数字媒体产品,改变你听到和看到的音频方式在游戏、电影、电视、演出和世界上任何地方的人们消费媒体。 影视配乐音效素材大全 百度一下 云桥网络 平台huo取 教程&#xff01; 所有采样均为&#xff1a;…

Rocksdb Ribbon Filter : 结合 XOR-filter 以及 高斯消元算法 实现的 高效filter

文章目录前言XOR-filter 实现原理xor filter 的构造原理xor filter 构造总结XOR-filter 和 ADD-filter对比XOR-filter 在计算上的优化Ribbon filter高斯消元法总结参考前言 还是起源于前几天的Rocksdb meetup&#xff0c;其中Peter C. Dillinger 这位大佬分享了自己为rocksdb实…

Revit和Unreal Engine真实的建筑可视化视频教程

Revit和Unreal Engine真实的建筑可视化视频教程 Lynda – Revit and Unreal Engine: Real-Life Architectural Visualizations Lynda–Revit和Unreal Engine&#xff1a;真实的建筑可视化 时长 3小时 25分 | 1.15 GB |含项目练习文件|使用的软件&#xff1a;Revit&#xff0c…

哈佛结构和冯诺依曼结构区别。

哈佛结构是一种将程序指令存储和数据存储分开的存储器结构。中央处理器首先到程序指令存储器中读取程序指令内容&#xff0c;解码后得到数据地址&#xff0c;再到相应的数据存储 器中读取数据&#xff0c;并进行下一步的操作&#xff08;通常是执行&#xff09;。程序指令存储和…

Blender灯光照明与渲染视频教程 Skillshare – Blender: Product rendering for beginners

Blender灯光照明与渲染视频教程 Skillshare – Blender: Product rendering for beginners Skillshare – Blender: Product rendering for beginners 教程大小&#xff1a;2.2G 共33小节课 1920X1080 mp4视频 语言&#xff1a;英语中文字幕 在这门课程中&#xff0c;你将了…

关于 linux io_uring 性能测试 及其 实现原理的一些探索

文章目录先看看性能AIO 的基本实现io_ring 使用io_uring 基本接口liburing 的使用io_uring 非poll 模式下 的实现io_uring poll模式下的实现io_uring 在 rocksdb 中的应用总结参考先看看性能 io_uring 需要内核版本在5.1 及以上才支持&#xff0c;liburing的编译安装 很简单&am…

c4d教程-太空火车站场景创作视频教程Skillshare – Create A Space Train Scene With Cinema 4D Redshift Render

c4d教程-太空火车站场景创作视频教程Skillshare – Create A Space Train Scene With Cinema 4D & Redshift Render 教程大小 1.66G 共15小节 1280X720 mp4 视频 语言&#xff1a;英语中文字幕 百度一下 云桥网络 平台huo取 教程&#xff01; Skillshare – Create A Spa…