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

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

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

多线程调度智能指针

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

#include 
#include 
#include 
#include 
#include 
#include 
#include #include 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是空的,则会先初始化一下,再做一些累加逻辑,处理完成之后再设置为空。

因为ThreadFunc中没有同步机制,我们多线程下可能的执行行为如下:

在这里插入图片描述

其中t1 < t2 < t3

因为我们在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();
}

更多相关:

  • C++11标准中可以为模板定义别名,比如 template using ptr=std::shared_ptr; //这里模板定义ptr为智能指针std::shared_ptr定义的别名 所以实际应用中可以借此来简化代码,比如 #include #include

  • 文章目录unique_ptr 智能指针的实现shared_ptr 智能指针的实现指针类型转换 unique_ptr 智能指针的实现 一个对象只能被单个unique_ptr 所拥有。 #include using namespace std;/*增加模板类型,保证智能指针的类型是由传入的类型决定的*/ t...

  • 文章目录weak_ptr描述声明作用原理实现函数成员使用总结 weak_ptr描述 声明 头文件: 模版类:template class weak_ptr 声明方式:std::weak_ptr statement 作用 根据boost库的官方描述,weak_ptr是...

  • 文章目录shared_ptr描述声明作用原理实现函数使用关于shared_ptr循环引用问题 shared_ptr描述 声明 shared_ptr属于C++11特性中新加的一种智能指针,它的实现方式是模版类,头文件 template class shared_ptr 所以使用shared...

  • enable_shared_from_this解析        enable_shared_from_this,是一个以其派生类为模板类型实参的基础模板,继承它,this指针就能变成shared_ptr。 什么时候该使用enable_shared_from_this模板类        在看下面的例子之前,简单说下使用背景,单有...

  • 经过长期探索,发现一个不需要手动设置线程休眠时间(e.g. std::this_thread::sleep_for(std::chrono::microseconds(1)))的代码: Github: https://github.com/log4cplus/ThreadPool #ifndef THREAD_POOL_H_7e...

  • nth_element(first,nth,last) first,last 第一个和最后一个迭代器,也可以直接用数组的位置。  nth,要定位的第nn 个元素,能对它进行随机访问. 将第n_thn_th 元素放到它该放的位置上,左边元素都小于它,右边元素都大于它. 测试代码: http://www.cplusplus.com...

  • c/c++老版本的rand()存在一定的问题,在转换rand随机数的范围,类型或者分布时,常常会引入非随机性。 定义在 中的随机数库通过一组协作类来解决这类问题:随机数引擎 和 随机数分布类 一个给定的随机数发生器一直会生成相同的随机数序列。一个函数如果定义了局部的随机数发生器,应该将(引擎和分布对象)定义为 st...

  • jsoncpp 是一个C++ 语言实现的json库,非常方便得支持C++得各种数据类型到json 以及 json到各种数据类型的转化。 一个json 类型的数据如下: {"code" : 10.01,"files" : "","msg" : "","uploadid" : "UP000000" } 这种数据类型方便我们人阅读以...

  • 问题如下: 已知一组数(其中有重复元素),求这组数可以组成的所有子集中,子 集中的各个元素和为整数target的子集,结果中无重复的子集。 例如: nums[] = [10, 1, 2, 7, 6, 1, 5], target = 8 结果为: [[1, 7], [1, 2, 5], [2, 6], [1, 1, 6]] 同样之前有...