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 ...
随机推荐
- Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题 ?
受现代 JavaScript 的限制 ,Vue 无法检测到对象属性的添加或删除.由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 ...
- Maven Helper插件——实现一键Maven依赖冲突问题
业余在一个SpringBoot项目集成Swagger2时,启动过程一直出现以下报错信息-- An attempt was made to call a method that does not exi ...
- Windows在待机后重新进入桌面出现资源管理器无响应的解决方案
问题 在日常使用Windows操作系统的过程中,我们可能会遇到一种较为特殊的情况--在系统待机后重新激活桌面时,资源管理器出现无响应现象.这一问题不仅影响用户体验,还可能导致剪切板功能异常,进而影响到 ...
- django 计算两个TimeField的时差
在 Django 中,你可以使用 datetime 模块来计算两个 TimeField 字段的时间差.以下是一个示例: from datetime import datetime, timedelta ...
- TokenObtainPairSerialize和TokenObtainPairView
TokenObtainPairSerializer和TokenObtainPairView是Django REST framework的SimpleJWT库提供的两个相关的类. TokenObtain ...
- 基于Java+SpringBoot+Vue宠物咖啡馆平台设计和实现
\n文末获取源码联系 感兴趣的可以先收藏起来,大家在毕设选题,项目以及论文编写等相关问题都可以给我加好友咨询 系统介绍: 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成 ...
- 基于Java网络书店商城设计实现(源码+lw+部署文档+讲解等)
系统介绍: 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势:对于网络书店商城当然也不能排除在外,随着网络技术的不断成熟,带动了网络书店商城,它彻底改变了过去传统 ...
- Git 克隆仓库报unable to get local issuer certificate错误解决方法
Git 克隆仓库报unable to get local issuer certificate错误解决方法 By:授客 QQ:1033553122 问题描述 克隆gitlab上的仓库,报错,如下 $ ...
- 【MySQL】重装Win10系统后恢复MySQL
因为种种原因把系统重装了,安装的MySQL不在C盘中,所以数据不会被系统格式化掉 但是重装系统就把之前CMD声明的MySQL服务给删除了 要让MySQL重新跑起来,就需要重新安装服务 恢复MYSQL博 ...
- 【SpringBoot】01 快速上手
环境搭建: JDK8 + IDEA 2018 + SpringBoot + Maven 3.0 + 创建Boot项目 2020.6.1更新补充: 最近才发现SpringBoot用IDEA构建项目会发生 ...