簡短的回答是:先釋放鎖,再通知(notify_one 或 notify_all)通常是更優的選擇。 雖然標準允許兩種順序,但“先解鎖,后通知”的性能通常更好。
下面我們來詳細解釋原因和兩種方式的區別。
- 先通知,后釋放鎖 (notify then unlock)
// 假設 mu 是一個 std::mutex, cv 是一個 std::condition_variable
{std::lock_guard<std::mutex> lk(mu); // 持有鎖// ... 修改共享數據 ...ready = true;cv.notify_one(); // 1. 先通知(鎖仍被持有)
} // 2. lock_guard 超出作用域,自動釋放鎖
可能發生的問題:性能損耗(“驚群”效應與立即阻塞)
當你在持有鎖的情況下調用 notify_one():
- 系統會喚醒一個(或多個,如果是 notify_all)正在等待(waiting)的線程。
- 被喚醒的線程會立即嘗試獲取與條件變量關聯的互斥鎖(這是 std::condition_variable::wait 操作的固有步驟)。
- 但是,此時通知線程還持有這把鎖!所以被喚醒的線程無法立即獲取鎖,它會被迫再次進入阻塞(blocking)狀態,等待通知線程釋放鎖。
- 通知線程在 } 處釋放鎖。
- 被喚醒的線程終于可以再次嘗試并成功獲取鎖。
這個過程導致了一次無謂的上下文切換:被喚醒的線程什么都沒做就又被阻塞了。在高性能要求的場景下,這種額外的切換會帶來不必要的開銷。
- 先釋放鎖,后通知 (unlock then notify)
{std::unique_lock<std::mutex> lk(mu); // 持有鎖// ... 修改共享數據 ...ready = true;lk.unlock(); // 1. 手動先釋放鎖cv.notify_one(); // 2. 再通知(鎖已被釋放)
}
// 或者使用一個額外的作用域讓 lock_guard 提前釋放
{{std::lock_guard<std::mutex> lk(mu);// ... 修改共享數據 ...ready = true;} // 鎖在這里被釋放cv.notify_one(); // 然后在沒有鎖的情況下通知
}
優點:性能更優
當你先釋放鎖再通知:
- 鎖已經被釋放。
- 調用 notify_one() 喚醒一個等待線程。
- 被喚醒的線程嘗試獲取互斥鎖,此時鎖是空閑的,所以它有很大概率能立即成功獲取并繼續執行,避免了不必要的二次阻塞和上下文切換。
這使得線程調度更加高效。