C++ shared_ptr是线程安全的吗?
导读:C++面试中有时会有这样一个问题,shared_ptr是线程安全的吗?对此问题,我们需要从三个并发场景进行考虑,拷贝shared_ptr的安全性、对shared_ptr赋值的安全性和读写shared_ptr指向内存区域的安全性。
对于以上问题,首先给出以下结论:
如果多个线程同时拷贝同一个shared_ptr对象,不会有问题,因为shared_ptr的引用计数是线程安全的。
如果多个线程同时修改同一个shared_ptr 对象,不是线程安全的。
如果多个线程同时读写shared_ptr指向的内存对象,不是线程安全的。
下面通过简单程序实验证明:
1. 引用计数更新,线程安全
这里我们讨论对shared_ptr进行拷贝的情况,由于此操作读写的是引用计数,而引用计数的更新是原子操作,因此这种情况是线程安全的。下面这个例子,两个线程同时对同一个shared_ptr进行拷贝,引用计数的值总是20001。
std::shared_ptr<int> p = std::make_shared<int>(0);
constexpr int N = 10000;
std::vector<std::shared_ptr<int>> sp_arr1(N);
std::vector<std::shared_ptr<int>> sp_arr2(N);
void increment_count(std::vector<std::shared_ptr<int>>& sp_arr) {
for (int i = 0; i < N; i++) {
sp_arr[i] = p;
}
}
std::thread t1(increment_count, std::ref(sp_arr1));
std::thread t2(increment_count, std::ref(sp_arr2));
t1.join();
t2.join();
std::cout<< p.use_count() << std::endl; // always 20001
2. 同时读写内存区域,线程不安全
下面这个例子,两个线程同时对同一个shared_ptr指向内存的值进行自增操作,最终的结果不是我们期望的20000。因此同时修改shared_ptr指向的内存区域不是线程安全的。
std::shared_ptr<int> p = std::make_shared<int>(0);
void modify_memory() {
for (int i = 0; i < 10000; i++) {
(*p)++;
}
}
std::thread t1(modify_memory);
std::thread t2(modify_memory);
t1.join();
t2.join();
std::cout << "Final value of p: " << *p << std::endl; // possible result: 16171, not 20000
3. 直接修改shared_ptr对象本身的指向,线程不安全。
下面这个程序示例,两个线程同时修改同一个shared_ptr对象的指向,程序发生了异常终止。
std::shared_ptr<int> sp = std::make_shared<int>(1);
auto modify_sp_self = [&sp]() {
for (int i = 0; i < 1000000; ++i) {
sp = std::make_shared<int>(i);
}
};
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(modify_sp_self);
}
for (auto& t : threads) {
t.join();
}
报错为:
pure virtual method called
terminate called without an active exception
用gdb查看函数调用栈,发现是在调用std::shared_ptr<int>::~shared_ptr()时出错,
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007ffff7bc7859 in __GI_abort () at abort.c:79
#2 0x00007ffff7e73911 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007ffff7e7f38c in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007ffff7e7f3f7 in std::terminate() () from /lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007ffff7e80155 in __cxa_pure_virtual () from /lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x00005555555576c2 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() ()
#7 0x00005555555572fd in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() ()
#8 0x0000555555557136 in std::__shared_ptr<int, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() ()
#9 0x000055555555781c in std::__shared_ptr<int, (__gnu_cxx::_Lock_policy)2>::operator=(std::__shared_ptr<int, (__gnu_cxx::_Lock_policy)2>&&) ()
#10 0x00005555555573d0 in std::shared_ptr<int>::operator=(std::shared_ptr<int>&&) ()
#11 0x000055555555639f in main::{lambda()#1}::operator()() const ()
...
其原因为:在并发修改的情况下,对正在析构的对象再次调用析构函数,导致了未定义的行为,从而发生了此异常。
对程序加锁后,程序可正常运行:
std::shared_ptr<int> sp = std::make_shared<int>(1);
std::mutex m;
auto modify = [&sp]() {
// make the program thread safe
std::lock_guard<std::mutex> lock(m);
for (int i = 0; i < 1000000; ++i) {
sp = std::make_shared<int>(i);
}
};
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(modify);
}
for (auto& t : threads) {
t.join();
}
std::cout << *sp << std::endl; // running as expected, result: 999999
总结
- shared_ptr未对指向的对象内存区域有线程安全保护,因此并发读写对应内存区域是不安全的。
- 由于赋值操作涉及原内存释放、修改指针指向等多个修改操作,其过程不是原子操作,因此对shared_ptr进行并发赋值不是线程安全的。
- 对shared_ptr进行并发拷贝,对数据指针和控制块指针仅进行读取并复制,然后对引用计数进行递增,而引用计数增加是原子操作。因此是线程安全的。
你好,我是七昂,计算机科学爱好者,致力于分享C/C++、操作系统、技术架构等计算机基础知识。如果你有任何问题或者建议,欢迎随时与我交流。如果这篇内容对您有帮助,希望能得到您的点赞和关注,之后将会持续分享更多干货。感谢阅读和支持!希望我们能一起探索程序员修炼之道。
C++ shared_ptr是线程安全的吗?的更多相关文章
- shared_ptr的线程安全
1.9 再论shared_ptr 的线程安全 虽然我们借shared_ptr 来实现线程安全的对象释放,但是shared_ptr 本身不是100% 线程安全的.它的引用计数本身是安全且无锁的,但对象的 ...
- shared_ptr的线程安全性
一: All member functions (including copy constructor and copy assignment) can be called by multiple t ...
- shared_ptr智能指针源码剖析
(shared_ptr)的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr 有两个数据成员,读写操作不能原子化.根据文档 (http://www.boost.org/doc/ ...
- 为什么多线程读写 shared_ptr 要加锁?
https://www.cnblogs.com/Solstice/archive/2013/01/28/2879366.html 为什么多线程读写 shared_ptr 要加锁? 陈硕(giantch ...
- C++之shared_ptr总结
转自 http://blog.csdn.net/u013696062/article/details/39665247 Share_ptr也是一种智能指针.类比于auto_ptr学习.所以推荐先学习a ...
- shared_ptr 和auto_ptr智能指针
shared_ptr:计数的智能指针 它是一个包装了new操作符在堆上分配的动态对象,但它实现的是引用计数型的智能指针 ,可以被自由地拷贝和赋值,在任意的地方共享它,当没有代码使用(引用计数为0)它时 ...
- shared_ptr 用法
引入 shared_ptr 是c++为了提高安全性而添加的智能指针,方便了内存管理. 特点 shared_ptr 是通过指针保持对象共享所有权的智能指针.多个 shared_ptr 对象可占有同一对象 ...
- 深入理解智能指针之shared_ptr(一)
本文基于C++标准库源码分析shared_ptr,旨在搞清楚shared_ptr是什么,线程安全性等,目标能够安全的使用智能指针. (一)shared_ptr是一个类. 首先可以确定的是shared_ ...
- C++11智能指针
今晚跟同学谈了一下智能指针,突然想要看一下C++11的智能指针的实现,因此下了这篇博文. 以下代码出自于VS2012 <memory> template<class _Ty> ...
- Boost使用笔记(Smart_ptr)
我是Word写的,复制过来实在懒得在排版了,有兴趣的朋友可以去我的百度文库看,谢谢 http://wenku.baidu.com/view/34e485e2f61fb7360b4c653e.html ...
随机推荐
- 详解Web应用安全系列(9)点击劫持
点击劫持(Clickjacking)漏洞,也被称为界面伪装攻击(UI Redress Attack)或UI覆盖攻击,是一种利用视觉欺骗手段进行的网络攻击方式.这种攻击方式通过技术手段欺骗用户点击他们本 ...
- 松灵机器人scout mini小车 自主导航(3)——建图导航仿真
松灵机器人Scout mini小车建图导航仿真 在之前的文章中,我们已经介绍了如何在gazebo和rviz对scout mini小车进行仿真,并且测试了添加自定义的传感器,在本文章中将进一步介绍如何利 ...
- 「比赛记录」CF Round 954 (Div. 3)
Codeforces Round 954 (Div. 3) 题目列表: A. X Axis B. Matrix Stabilization C. Update Queries D. Mathemati ...
- TIER 1: Sequel
TIER 1: Sequel MySQL MySQL 是一种流行的开源关系型数据库管理系统(RDBMS),它被广泛用于存储和管理大量的结构化数据.MySQL 是由瑞典公司 MySQL AB 开发的,后 ...
- ngnix简介和基础
一.Nginx简介 Nginx 是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/SMTP代理服务器 是一个模块化软件 [1].安装nginx 使用源码包编译安装 cd /opt ...
- java srpint boot 2.2.1 第二部份,乐观锁机制, 构造复杂查询条件,分页查询 相关内容,删除与软删除
第二部份,引起锁机制的原理和解决方案: 测试环境搭建第一步先建一个数据库表用于模拟商品购买. CREATE TABLE product ( id INT AUTO_INCREMENT PRIMARY ...
- leetcode2397. 被列覆盖的最多行数 回溯法/枝剪
第一次手搓一个回溯法,超时后采用枝剪勉强通过 class Solution { int max=0; int numSelect; public int maximumRows(int[][] mat ...
- 将txt转化为csv的方法和遇到问题
一.无法修改扩展名步骤如下 二.转换之后所有数据都挤在第一列 win10系统修改文件扩展名只需4部,打开我的电脑->查看->选择->查看->取消勾选(已知隐藏文件的扩展名)-& ...
- a-from提交时遇到errorFields:[]验证错误(vue3)
应用场景:使用a-form组件,里面使用a-select组件:当a-select组件内的值发生改变时,调用a-form的验证表单,进而提交. 问题:提交时遇到errorFields:[]验证错误 解决 ...
- SQL提高查询性能的几种方式
创建索引,提高性能 索引可以极大地提高查询性能,其背后的原理: 索引是的数据库引擎能够快速的找到表中的数据,它们类似于书籍的目录,使得你不需要逐页查找所需要的信息 索引能够帮助数据库引擎直接定位到所需 ...