← SEARCHABLE ARCHIVE
Operating System · 2026-07-15

執行緒同步和競爭條件

為什麼每次運行共享資料結​​果都會改變?

INTERACTIVE READING MODE →

該部落格上撰寫的文章是出於教學目的而創建的。 另外,文章中的資訊可能不正確,請謹慎。

同一進程中的執行緒共享堆和全域資料。 因此,資料可以快速交換,但如果同時讀取和寫入相同的值,則每次執行的結果可能會有所不同。

這個問題稱為競爭條件。

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

您需要養成始終以相同順序獲取鎖的習慣,並且在持有鎖時不要執行長時間運行的任務。

多線程的重點不是創建很多線程。 減少共享數據,明確定義必須共享的範圍,並使用適合該範圍的同步工具。