关于 智能指针 的线程安全问题
先說結論,智能指針都是非線程安全的。
多線程調度智能指針
這里案例使用的是shared_ptr,其他的unique_ptr或者weak_ptr的結果都是類似的,如下多線程調度代碼:
#include <memory>
#include <thread>
#include <vector>
#include <atomic>
#include <mutex>
#include <unistd.h>
#include <iostream>#include <iostream>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,每次執行之前如果發現這個ptr是空的,則會先初始化一下,再做一些累加邏輯,處理完成之后再設置為空。
因為ThreadFunc中沒有同步機制,我們多線程下可能的執行行為如下:
其中t1 < t2 < t3 <t4,也就是有可能thread1 在t3時刻 reset了一個空的ptr, 但是在t4時刻,thread2根據自己之前t2時判斷的ptr不為空的邏輯,直接使用了空的ptr去訪問數據,從而造成BAD_ACCESS問題。
因為我們在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();
}
總結
以上是生活随笔為你收集整理的关于 智能指针 的线程安全问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我爸说存3000块钱60年定期,60年后
- 下一篇: 怎么合理安排时间