修改指向
修改指向分为操作同一个shared_ptr对象和操作不同的shared_ptr对象两种 。
多线程代码操作的是同一个shared_ptr的对象
比如std::thread的回调函数,是一个lambda表达式,其中引用捕获了一个shared_ptr对象
shared_ptr<A> sp1 = make_shared<A>();std::thread td([&sp1] () {....});又或者通过回调函数的参数传入的shared_ptr对象 , 参数类型是指针或引用:
`指针类型:void fn(shared_ptr<A>* sp) { ... }std::thread td(fn, &sp1);引用类型:void fn(shared_ptr<A>& sp) { ... }std::thread td(fn, std::ref(sp1));`
当你在多线程回调中修改shared_ptr指向的时候,这时候确实不是线程安全的 。
void fn(shared_ptr<A>& sp) { if (..) { sp = other_sp; } else if (...) { sp = other_sp2; }}shared _ptr内数据指针要修改指向,sp原先指向的引用计数的值要减去1,other_sp指向的引用计数值要加1 。然而这几步操作加起来并不是一个原子操作,如果多个线程都在修改sp的指向的时候,那么有可能会出问题 。比如在导致计数在操作-1的时候 , 其内部的指向已经被其他线程修改过了,引用计数的异常会导致某个管理的对象被提前析构,后续在使用到该数据的时候触发coredump 。当然如果你没有修改指向的时候,是没有问题的 。也就是:
同一个shared_ptr对象被多个线程同时读是安全的
同一个shared_ptr对象被多个线程同时读写是不安全的
多线程代码操作的不是同一个shared_ptr的对象 。
这里指的是管理的数据是同一份 , 而shared_ptr不是同一个对象,比如多线程回调的lambda是按值捕获的对象 。
std::thread td([sp1] () {....});或者参数传递的shared_ptr是值传递,而非引用:
void fn(shared_ptr<A> sp) { ...}std::thread td(fn, sp1);这时候每个线程内看到的sp,他们所管理的是同一份数据,用的是同一个引用计数 。但是各自是不同的对象,当发生多线程中修改sp指向的操作的时候,是不会出现非预期的异常行为的 。也就是说,如下操作是安全的:
void fn(shared_ptr<A> sp) { if (..) { sp = other_sp; } else if (...) { sp = other_sp2; }}尽管前面我们提到了如果是按值捕获(或传参)的shared_ptr对象,那么该对象是线程安全的,然而话虽如此,但却可能让人误入歧途 。因为我们使用shared_ptr更多的是操作其中的数据,对齐管理的数据进行读写,尽管在按值捕获的时候shared_ptr是线程安全的 , 我们不需要对此施加额外的同步操作(比如加解锁),但是这并不意味着shared_ptr所管理的对象是线程安全的!请注意这是两回事 。
最后再来看下std官方手册是怎么讲的:
All member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of shared_ptr without additional synchronization even if these instances are copies and share ownership of the same object. If multiple threads of execution access the same instance of shared_ptr without synchronization and any of those accesses uses a non-const member function of shared_ptr then a data race will occur; the shared_ptr overloads of atomic functions can be used to prevent the data race.这段话的意思是,shared_ptr 的所有成员函数(包括复制构造函数和复制赋值运算符)都可以由多个线程在不同的 shared_ptr 实例上调用,即使这些实例是副本并且共享同一个对象的所有权 。如果多个执行线程在没有同步的情况下访问同一个 shared_ptr 实例,并且这些访问中的任何一个使用了 shared_ptr 的非 const 成员函数,则会发生数据竞争;可以使用shared_ptr的原子函数重载来防止数据竞争 。
我们可以得到下面的结论:
1. 多线程环境中,对于持有相同裸指针的std::shared_ptr实例,所有成员函数的调用都是线程安全的 。
a. 当然,对于不同的裸指针的 std::shared_ptr 实例,更是线程安全的
b. 这里的 “成员函数” 指的是 std::shared_ptr 的成员函数,比如 get ()、reset ()、operrator->()等
推荐阅读
- 沙滩裤最常见的面料有哪些 沙滩裤首选什么面料
- 7种常见水果,秋天吃正是好时候,健康促消化对吗
- 常见鸟类的本领和特征 常见鸟类的本领
- 八个 C++ 开源项目,帮助初学者进阶成长
- 买车分期与全款:内行揭秘,新手购车避坑指南
- 常见泵原理图 泵的结构与原理图
- U盘使用过程中常见问题及其解决方案
- SSL协议是什么?关于SSL和TLS的常见问题解答
- 衣服的常见面料,十大常见服装面料优缺点分析
- C++多线程编程:解锁性能与并发的奥秘
