文章目錄
- 條件變量概述
- 條件變量簡介
- 條件變量的基本用法
- 案例:兩個線程交替打印奇偶數
- 代碼解釋
- `std::unique_lock::try_lock_until` 介紹
- 代碼示例
- 代碼解釋
- 注意事項
- `std::condition_variable::wait` 詳細解析與示例
- `std::condition_variable::wait` 接口介紹
- 代碼示例
- 代碼解釋
- 關于最后一個互斥鎖可能再次阻塞線程
- 條件變量其他接口的應用示例
- `wait_for` 接口應用
- `wait_until` 接口應用
- `notify_one` 接口應用
- `notify_all` 接口應用
條件變量概述
在 C++ 多線程編程里,同步機制是極為關鍵的部分,它能保證多個線程安全且高效地訪問共享資源。其中,條件變量(std::condition_variable
)和 std::unique_lock::try_lock_until
是很實用的工具。接下來,我們會深入探討它們的應用。
條件變量簡介
條件變量是 C++ 標準庫中的一個同步原語,它可讓線程在特定條件達成時被喚醒。其主要用途是線程間的等待 - 通知機制,一個線程等待某個條件成立,而另一個線程在條件成立時通知等待的線程。
條件變量的基本用法
條件變量的基本操作包含 wait
、wait_for
、wait_until
和 notify_one
/notify_all
。
wait
:使線程進入等待狀態,同時釋放互斥量,直到被其他線程喚醒。wait_for
:線程會等待一段時間,若在這段時間內被通知則繼續執行,若超時則繼續執行。wait_until
:線程會等待到指定的時間點,若在該時間點前被通知則繼續執行,若到達時間點還未被通知則繼續執行。notify_one
:喚醒一個正在等待的線程。notify_all
:喚醒所有正在等待的線程。
案例:兩個線程交替打印奇偶數
以下是一個使用條件變量實現兩個線程交替打印奇偶數的案例:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>using namespace std;
void two_thread_print()
{std::mutex mtx;condition_variable c;int n = 100;bool flag = true;thread t1([&]() {int i = 0;while (i < n){unique_lock<mutex> lock(mtx);c.wait(lock, [&]()->bool {return flag; });cout << i << endl;flag = false;i += 2; // 偶數c.notify_one();}});thread t2([&]() {int j = 1;while (j < n){unique_lock<mutex> lock(mtx);c.wait(lock, [&]()->bool {return !flag; });cout << j << endl;flag = true;j += 2; // 奇數c.notify_one();}});t1.join();t2.join();
}int main()
{two_thread_print();return 0;
}
代碼解釋
- 線程函數:
t1
線程負責打印偶數,它會等待flag
為true
時開始打印,打印完成后將flag
置為false
,并通知另一個線程。t2
線程負責打印奇數,它會等待flag
為false
時開始打印,打印完成后將flag
置為true
,并通知另一個線程。
- 條件變量的使用:
c.wait(lock, [&]()->bool {return flag; });
和c.wait(lock, [&]()->bool {return !flag; });
用于線程的等待,當條件不滿足時線程會進入等待狀態,同時釋放互斥量。c.notify_one();
用于喚醒另一個等待的線程。
- 線程同步:
- 通過
t1.join()
和t2.join()
確保主線程等待兩個子線程執行完畢。
- 通過
std::unique_lock::try_lock_until
介紹
std::unique_lock::try_lock_until
是 C++ 標準庫 <mutex>
頭文件中的一部分,用于嘗試在指定的時間點之前鎖定關聯的互斥量。如果在指定時間之前成功鎖定,它會返回 true
;若超時仍未鎖定,則返回 false
。
代碼示例
下面的代碼模擬了一個多線程場景,主線程會嘗試在指定時間內鎖定互斥量,而工作線程會先占用一段時間互斥量。
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>std::mutex mtx;void worker() {std::this_thread::sleep_for(std::chrono::seconds(2));std::lock_guard<std::mutex> lock(mtx);std::cout << "Worker thread locked the mutex." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));std::cout << "Worker thread unlocked the mutex." << std::endl;
}int main() {std::thread t(worker);std::unique_lock<std::mutex> lock(mtx, std::defer_lock);auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(1);if (lock.try_lock_until(timeout)) {std::cout << "Main thread locked the mutex." << std::endl;lock.unlock();} else {std::cout << "Main thread failed to lock the mutex within the timeout." << std::endl;}t.join();return 0;
}
代碼解釋
- 線程函數
worker
:工作線程會先休眠 2 秒,然后鎖定互斥量mtx
,打印信息,再休眠 2 秒,最后解鎖互斥量。 - 主線程邏輯:
- 創建工作線程
t
。 - 創建
std::unique_lock
對象lock
,初始時不鎖定互斥量。 - 設定超時時間為當前時間加上 1 秒。
- 調用
try_lock_until
嘗試在超時時間之前鎖定互斥量。 - 根據返回結果輸出相應信息。
- 創建工作線程
- 線程同步:主線程通過
t.join()
等待工作線程結束。
注意事項
- 此函數適用于需要在一定時間內嘗試鎖定互斥量的場景,避免無限期等待。
- 超時時間點可以使用不同的時鐘類型和時間單位,如
std::chrono::steady_clock
或std::chrono::system_clock
。
std::condition_variable::wait
詳細解析與示例
std::condition_variable::wait
接口介紹
std::condition_variable::wait
有兩種重載形式:
// 無條件形式
void wait (unique_lock<mutex>& lck);
// 帶謂詞形式
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);
當前線程(應已鎖定互斥鎖)的執行將被阻止,直到收到通知。在阻塞線程的那一刻,該函數會自動調用 lck.unlock()
,允許其他鎖定的線程繼續。一旦收到通知(顯式地,由其他線程通知),該函數就會解除阻塞并調用 lck.lock()
,離開時的狀態與調用函數時的狀態相同。然后函數返回(請注意,最后一個互斥鎖可能會在返回之前再次阻塞線程)。
通常,通過在另一個線程中調用 notify_one
或 notify_all
來通知函數喚醒。但某些實現可能會在不調用這些函數的情況下產生虛假的喚醒調用。因此,此功能的用戶應確保滿足其恢復條件。
如果指定了帶謂詞的版本(2),則該函數僅在 pred()
返回 false
時阻塞,并且通知只能在線程變為 pred()
返回 true
時取消阻塞線程(這對于檢查虛假喚醒調用特別有用)。
代碼示例
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void worker() {std::unique_lock<std::mutex> lock(mtx);// 使用帶謂詞的 wait 版本cv.wait(lock, []{ return ready; });std::cout << "Worker thread is working." << std::endl;
}int main() {std::thread t(worker);{std::lock_guard<std::mutex> lock(mtx);ready = true;}cv.notify_one();t.join();return 0;
}
代碼解釋
worker
線程:worker
線程獲取unique_lock
并調用cv.wait(lock, []{ return ready; });
,如果ready
為false
,線程會進入等待狀態,同時釋放互斥鎖。當收到通知并且ready
變為true
時,線程會重新獲取互斥鎖并繼續執行。- 主線程:主線程設置
ready
為true
并調用cv.notify_one()
通知等待的線程。
關于最后一個互斥鎖可能再次阻塞線程
在 cv.wait(lock, []{ return ready; });
中,當收到通知且謂詞 []{ return ready; }
返回 true
時,wait
函數會嘗試重新鎖定互斥鎖(調用 lck.lock()
)。如果此時互斥鎖被其他線程持有,當前線程會再次被阻塞,直到成功獲取互斥鎖。
條件變量其他接口的應用示例
wait_for
接口應用
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void worker() {std::unique_lock<std::mutex> lock(mtx);if (cv.wait_for(lock, std::chrono::seconds(2), []{ return ready; })) {std::cout << "Worker thread got notified in time." << std::endl;} else {std::cout << "Worker thread timed out." << std::endl;}
}int main() {std::thread t(worker);std::this_thread::sleep_for(std::chrono::seconds(3));{std::lock_guard<std::mutex> lock(mtx);ready = true;}cv.notify_one();t.join();return 0;
}
這里 worker
線程會等待 2 秒,如果在這 2 秒內沒有收到通知,就會超時繼續執行。
wait_until
接口應用
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>std::timed_mutex mtx;
bool ready = false;void worker() {std::this_thread::sleep_for(std::chrono::seconds(2));std::lock_guard<std::timed_mutex> lock(mtx); // 使用 std::timed_mutexstd::cout << "Worker thread locked the mutex." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));ready = true;std::cout << "Worker thread unlocked the mutex." << std::endl;
}int main() {std::thread t(worker);std::unique_lock<std::timed_mutex> lock(mtx, std::defer_lock); // 使用 std::timed_mutexauto timeout = std::chrono::system_clock::now() + std::chrono::seconds(1);if (lock.try_lock_until(timeout)) { // 正確:std::timed_mutex 支持 try_lock_untilstd::cout << "Main thread locked the mutex." << std::endl;lock.unlock();}else {std::cout << "Main thread failed to lock the mutex within the timeout." << std::endl;}t.join();return 0;
}
worker
線程會等待到指定的時間點,如果在該時間點前收到通知則繼續執行,否則超時繼續執行。
notify_one
接口應用
參考前面兩個線程交替打印奇偶數的例子,c.notify_one();
用于喚醒另一個等待的線程。
notify_all
接口應用
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void worker(int id) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, []{ return ready; });std::cout << "Worker thread " << id << " is working." << std::endl;
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 3; ++i) {threads.emplace_back(worker, i);}{std::lock_guard<std::mutex> lock(mtx);ready = true;}cv.notify_all();for (auto& t : threads) {t.join();}return 0;
}
在這個例子中,notify_all
會喚醒所有等待的線程。
通過條件變量和 std::unique_lock::try_lock_until
,我們能夠更靈活地實現多線程同步,避免死鎖和資源競爭問題。在實際開發中,根據具體需求合理運用這些工具,可以提高程序的性能和穩定性。