執行緒同步和競爭條件
為什麼每次運行共享資料結果都會改變?
該部落格上撰寫的文章是出於教學目的而創建的。 另外,文章中的資訊可能不正確,請謹慎。
同一進程中的執行緒共享堆和全域資料。 因此,資料可以快速交換,但如果同時讀取和寫入相同的值,則每次執行的結果可能會有所不同。
這個問題稱為競爭條件。
count++ 不是一次操作
int count = 0;
void AddCount()
{
++count;
}
它可能是一行程式碼,但在 CPU 上可能是多個步驟:讀取一個值,加 1,然後再次儲存它。
線程 A 讀取計數 0
線程 B 讀取計數 0
線程A存儲1
線程B存儲1
我們將其遞增兩次,但最終結果是 1,而不是 2。 由於執行緒的執行順序會根據作業系統狀況而變化,這可能是測試過程中偶爾出現的錯誤。
互斥鎖一次只允許一個執行緒進入
std::mutex countMutex;
int count = 0;
void AddCount()
{
std::lock_guard<std::mutex> lock(countMutex);
++count;
}
當鎖定互斥鎖的執行緒執行臨界區時,其他執行緒會等待。 lock_guard如果使用 ,即使函數結束或發生異常,鎖也會自動釋放。
原子可用於簡單值
原子操作可用於小型操作,例如增加整數。
std::atomic<int> count = 0;
void AddCount()
{
++count;
}
然而,如果需要同時安全地維護多個變數之間的關係,那麼僅用一個原子是無法解決的。 例如,如果金幣扣除額和物品添加必須同時成功,則兩者都必須受到單一關鍵區域的保護。
您也應該小心死鎖
如果兩個執行緒等待另一個執行緒持有的鎖,則沒有人可以繼續。 這稱為死鎖。
線程A:持有鎖1並等待鎖2
線程B:持有鎖2並等待鎖1
您需要養成始終以相同順序獲取鎖的習慣,並且在持有鎖時不要執行長時間運行的任務。
多線程的重點不是創建很多線程。 減少共享數據,明確定義必須共享的範圍,並使用適合該範圍的同步工具。