C++併發編程學習03:一個簡易的線程安全棧實現

線程安全棧

學習內容來自於“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.使用鎖層次
// 將應用程序分層,並且確認所有能夠在任意給定的層級上被鎖定的互斥元
// 當代碼試圖鎖定一個互斥元時,如果它(當前執行的代碼)在較低層已經持有鎖定,
// 則不允許它鎖定該互斥元。
// !!!!通過給每一個互斥元分配層號,並記錄下每個線程都鎖定了哪些互斥元,則可以在運行時
// 進行檢查
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章