shared_ptr 是线程安全的吗?
admin
2024-05-02 02:32:38
0

这个话题真的很老了,不过今天的实验还是没有得出结论,所以就简单的记录一下实验过程吧,希望能够抛砖引玉,静待评论区的大神更深入的分析。

本文在实验过程中,主要参考了c++ 11 的shared_ptr多线程安全?里,果冻虾仁的回复。

我们先限制一下这里讨论的线程安全问题的范围:只考虑 shared_ptr 所管理的指针本身的创建和销毁过程是否线程安全,即 shared_ptr 所管理的自增和自减部分是否是安全的。至于指针所指向的类的自身构造和析构过程的线程安全,不属于讨论范围

先 PO 完整的测试代码,再拆解来分析

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include using namespace std;void new_section(string section, string msg) {cout << endl;cout << "#################################################################" << endl;cout << "# [" << section << "] " << msg << endl;cout << "#################################################################" << endl;
}class Ref {
public:std::vector m_data;Ref(initializer_list l): m_data(l) {cout << "Ref(initializer_list l)" << endl;}string to_string() {stringstream ss;ss << "Ref(";bool first = true;for (auto v: m_data) {if (!first) { ss << ", "; }ss << v;}ss << ")";return ss.str();}
};shared_mutex gMutex;shared_ptr thread_func(shared_ptr& p) {  // demo according to https://www.zhihu.com/question/56836057cout << "ready" << endl;shared_lock lock(gMutex);  // comment this line out, see anything happenscout << "running" << endl;shared_ptr n{nullptr};n = p;p = nullptr;p = n;return n;
}int main()
{int nNumbers = 1000;int nThreads = 10000;int secsDelay = 5;list threads;{shared_ptr creator{new Ref{1,2,3}};for (int i = 0; i < nNumbers; ++i) {creator->m_data.push_back(i);}unique_lock lock(gMutex);for (int i = 0; i < nThreads; ++i) {threads.push_back(thread(bind(thread_func, std::ref(creator))));}for (int i = 0; i < secsDelay; ++i) {this_thread::sleep_for(chrono::seconds(1));cout << "counting " << i << endl;}}for (auto& t: threads) {t.join();}return 0;
}

首先,我们使用互斥量 gMutex 和锁 unique_lock 来完成操作的同时开始,类似于一个“起跑线”,让所有线程的 "n = 0; p = nullptr; p = n;" 操作,能够同时进行。


shared_mutex gMutex;shared_ptr thread_func(shared_ptr& p) {  // demo according to https://www.zhihu.com/question/56836057cout << "ready" << endl;// 关键行,让所有线程都在此处停留,用“读锁”保证“写锁”释放前,不会执行指针操作。//   同时保证,写锁释放后,所有线程能够并行的从这里开始执行(起跑)shared_lock lock(gMutex);cout << "running" << endl;shared_ptr n{nullptr};n = p;p = nullptr;p = n;return n;
}int main()
{int nNumbers = 1000;int nThreads = 10000;int secsDelay = 5;list threads;{shared_ptr creator{new Ref{1,2,3}};for (int i = 0; i < nNumbers; ++i) {creator->m_data.push_back(i);}// 用“写锁”,确保所有线程(包含“写锁”)不会执行,类似于画了一根“起跑线”unique_lock lock(gMutex);for (int i = 0; i < nThreads; ++i) {// 关键行,启动多个线程threads.push_back(thread(bind(thread_func, std::ref(creator)))); }for (int i = 0; i < secsDelay; ++i) {this_thread::sleep_for(chrono::seconds(1));cout << "counting " << i << endl;}// 利用锁的生命周期来解“写锁”,“写锁”接触后,所有线程即开始执行,类似于“起跑”}for (auto& t: threads) {t.join();}return 0;
}

本实验的测试结果,并未出现崩溃或者DEBUG报错。

但是从果冻虾仁的回复来看,最关键的,莫过于在 thread_func 里面,对入参 p 的两次赋值操作,即下面两行

    p = nullptr;p = n;

如果有异常情况,我的理解,应该是会有崩溃或者DEBUG的 Assert failuer 之类的错误。

所以从这个实验来看,应该还算是线程安全的。

当然,按照理论分析,问题确实看上去是有的,也可能是笔者的实验设定不能体现这个问题罢了。​​​​​​​

按照《c++ 11 的shared_ptr多线程安全?》里面其他回答来看,我们在跨线程的情况下,只要做到以下两点,应该就问题不大:

1. 传递指针的值

2. 不修改指针的值

那么如何修改这段代码呢?


shared_ptr thread_func(shared_ptr p) {  // @@@ 传指针的值cout << "ready" << endl;shared_lock lock(gMutex);cout << "running" << endl;shared_ptr n{p};// @@@ 删除原来的赋值操作return n;
}int main()
{int nNumbers = 1000;int nThreads = 10000;int secsDelay = 5;list threads;{shared_ptr creator{new Ref{1,2,3}};for (int i = 0; i < nNumbers; ++i) {creator->m_data.push_back(i);}unique_lock lock(gMutex);for (int i = 0; i < nThreads; ++i) {// @@@ 绑定时,也绑定值,而不是引用threads.push_back(thread(bind(thread_func, creator))); }for (int i = 0; i < secsDelay; ++i) {this_thread::sleep_for(chrono::seconds(1));cout << "counting " << i << endl;}}for (auto& t: threads) {t.join();}return 0;
}

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...