線程安全棧
學習內容來自於“C++併發編程實戰”
#pragma once
#include <exception>
#include <memory> // std::make_shared 和 智能指針
#include <mutex> // lock_guard
#include <stack>
struct empty_stack : std::exception
{
const char* what() const throw();
};
template<typename T>
class ThreadSafe_Stack
{
private:
std::stack<T> m_data;
mutable std::mutex m_mutex;
public:
ThreadSafe_Stack() {}
ThreadSafe_Stack(const ThreadSafe_Stack& other)
{
std::lock_guard<std::mutex> lock(other.m_mutex);
m_data = other.m_data; // 在構造函數體中執行復制
}
// 棧本身不能被賦值
ThreadSafe_Stack& operator=(const ThreadSafe_Stack&) = delete;
void push(T new_value)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_data.push(new_value);
}
// 通過使用std::shared_ptr<>使得允許棧來處理內存分配問題,避免了對new和delete的過多調用
std::shared_ptr<T> pop()
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_data.empty()) throw empty_stack(); // 執行pop的時候判斷棧是否爲空
//make_shared 在動態內存中分配一個對象並初始化,返回指向改對象的shared_ptr
std::shared_ptr<T> const res(std::make_shared<T>(m_data.top()));
m_data.pop();
return res;
}
void pop(T& value)
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_data.empty()) throw empty_stack();
value = m_data.top(); // 用標準庫棧stack??
m_data.pop();
}
bool empty() const
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_data.empty();
}
};
// 鎖粒度可能會造成的問題
// 1.單個的全局互斥元保護所有共享的數據
// 在一個有大量共享數據的系統中,可能會消除併發所有的性能優勢,因爲線程被限制爲每次只能運行一個
// 即使它們訪問了同一個數據的不同部分
// 多處理系統的Linux內核的第一個版本,使用了單個全局內核鎖,導致:一個雙處理器系統
// 通常比兩個單處理器系統的性能更差
// Linux內核的後續版本:轉移到一個更細粒度的鎖定方案,四個處理器的性能更接近於理想的
// 單處理器系統的4倍
// 死鎖的解決
// 1.避免嵌套鎖:如果已經持有一個鎖,就不再獲取鎖
// 互斥元鎖定是思索最常見的誘因
// 2.持有鎖的時候,避免調用用戶提供的代碼
// 自己在持有鎖的時候,若調用用戶提供的代碼,因爲用戶提供的代碼可能會獲取鎖
// 則可能出現嵌套鎖的情況
// 3.以固定的順序獲取鎖
// 如果需要獲取兩個或者更多的鎖,並且不能以std::lock的單個操作取得
// 次優的做法:在每個線程中以相同的順序獲取它們(避免成環)。
// 對鏈表數據加鎖的保護(也會造成死鎖:可以定義遍歷的順序)
// 1.爲鏈表上的每一個節點都設置一個互斥元
// 2.爲訪問這個鏈表,線程必須獲取它們感興趣的每個節點上的鎖
// 3.若某個線程需要刪除某個節點: 必須獲取待刪除的節點及其兩邊的節點共三個節點的鎖。
// 4.遍歷時,線程在獲取序列中下一個節點上的鎖的時候,必須保持當前節點上的鎖
// 以確保指向下一節點的指針在次期間不會被修改,一旦獲取到下一節點上的鎖,則可以釋放前面節點上的鎖
// 這種方式也可能會導致死鎖
// 4.使用鎖層次
// 將應用程序分層,並且確認所有能夠在任意給定的層級上被鎖定的互斥元
// 當代碼試圖鎖定一個互斥元時,如果它(當前執行的代碼)在較低層已經持有鎖定,
// 則不允許它鎖定該互斥元。
// !!!!通過給每一個互斥元分配層號,並記錄下每個線程都鎖定了哪些互斥元,則可以在運行時
// 進行檢查