C++ 中多線程編程的兩個核心同步原語:互斥鎖 (Mutex) 和 條件變量 (Condition Variable)。它們是實現線程間安全通信和協調的關鍵。
1. 互斥鎖 (Mutex)
核心概念
互斥鎖用于保護共享數據,確保同一時間只有一個線程可以訪問該數據,從而避免數據競爭 (Data Race)。
原理:線程在訪問共享數據前先上鎖 (lock),如果鎖已被其他線程占用,則當前線程阻塞 (block) 等待。訪問完成后解鎖 (unlock),讓其他線程有機會獲取鎖。
C++ 中的互斥鎖 (<mutex>
頭文件)
1. std::mutex
最基本的互斥鎖。
cpp
#include <iostream> #include <thread> #include <mutex>std::mutex g_mutex; // 全局互斥鎖 int shared_data = 0;void increment() {for (int i = 0; i < 100000; ++i) {g_mutex.lock(); // 上鎖++shared_data; // 臨界區代碼g_mutex.unlock(); // 解鎖} }int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final value: " << shared_data << std::endl; // 正確輸出 200000return 0; }
2. std::lock_guard
(推薦使用)
RAII 風格的鎖管理,在構造時自動上鎖,析構時自動解鎖,即使發生異常也能保證解鎖,避免死鎖。
cpp
void safe_increment() {for (int i = 0; i < 100000; ++i) {std::lock_guard<std::mutex> lock(g_mutex); // 構造時上鎖,析構時解鎖++shared_data;} // lock_guard 在此析構,自動解鎖 }
3. std::unique_lock
(更靈活)
比 lock_guard
更靈活,可以手動控制上鎖和解鎖的時機,是條件變量必需的伙伴。
cpp
void flexible_increment() {for (int i = 0; i < 100000; ++i) {std::unique_lock<std::mutex> lock(g_mutex); // 自動上鎖++shared_data;// 可以手動提前解鎖,不需要等到作用域結束lock.unlock();// ... 這里可以執行一些不涉及共享數據的操作} }
2. 條件變量 (Condition Variable)
核心概念
條件變量用于線程間的通信和協調。它允許一個線程等待某個條件成立,而其他線程在條件成立時通知等待的線程。
典型生產者-消費者模式:
消費者線程等待"緩沖區不為空"的條件
生產者線程在放入數據后通知消費者
C++ 中的條件變量 (<condition_variable>
頭文件)
基本使用模式
cpp
#include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue>std::mutex mtx; std::condition_variable cv; std::queue<int> data_queue; const int MAX_SIZE = 5;// 生產者線程 void producer() {for (int i = 0; i < 10; ++i) {std::unique_lock<std::mutex> lock(mtx);// 等待條件:隊列未滿// 使用lambda表達式作為等待條件cv.wait(lock, [] { return data_queue.size() < MAX_SIZE; });data_queue.push(i);std::cout << "Produced: " << i << std::endl;lock.unlock(); // 手動解鎖(可選,notify之前解鎖更好)cv.notify_one(); // 通知一個等待的消費者} }// 消費者線程 void consumer() {while (true) {std::unique_lock<std::mutex> lock(mtx);// 等待條件:隊列不為空cv.wait(lock, [] { return !data_queue.empty(); });int data = data_queue.front();data_queue.pop();std::cout << "Consumed: " << data << std::endl;lock.unlock();cv.notify_one(); // 通知生產者可能有空位了if (data == 9) break; // 收到最后一個數據后退出} }int main() {std::thread prod(producer);std::thread cons(consumer);prod.join();cons.join();return 0; }
條件變量的關鍵方法
wait(lock, predicate)
:原子地解鎖并阻塞當前線程
被喚醒后重新獲取鎖
檢查 predicate 條件,如果為 false 則繼續等待
notify_one()
:喚醒一個等待中的線程(如果有)
notify_all()
:喚醒所有等待中的線程
3. 為什么條件變量需要互斥鎖?
條件變量必須與互斥鎖配合使用,原因如下:
原子性操作:檢查條件和進入等待必須是原子操作,否則可能發生:
線程A檢查條件 → 條件不滿足
線程B修改條件并發出通知
線程A才開始等待 → 通知丟失,線程A永遠等待
保護共享狀態:條件變量等待的"條件"通常是共享數據,需要用互斥鎖保護。
4. 完整的生產者-消費者示例
cpp
#include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue>class ThreadSafeQueue { private:std::queue<int> queue_;std::mutex mtx_;std::condition_variable cv_producer_;std::condition_variable cv_consumer_;const int max_size_ = 5;bool stop_ = false;public:void push(int value) {std::unique_lock<std::mutex> lock(mtx_);cv_producer_.wait(lock, [this] { return queue_.size() < max_size_ || stop_; });if (stop_) return;queue_.push(value);std::cout << "Produced: " << value << std::endl;cv_consumer_.notify_one();}int pop() {std::unique_lock<std::mutex> lock(mtx_);cv_consumer_.wait(lock, [this] { return !queue_.empty() || stop_; });if (stop_ && queue_.empty()) return -1;int value = queue_.front();queue_.pop();std::cout << "Consumed: " << value << std::endl;cv_producer_.notify_one();return value;}void stop() {std::lock_guard<std::mutex> lock(mtx_);stop_ = true;cv_producer_.notify_all();cv_consumer_.notify_all();} };int main() {ThreadSafeQueue queue;std::thread producer([&queue] {for (int i = 0; i < 10; ++i) {queue.push(i);std::this_thread::sleep_for(std::chrono::milliseconds(100));}queue.stop();});std::thread consumer([&queue] {while (true) {int value = queue.pop();if (value == -1) break;std::this_thread::sleep_for(std::chrono::milliseconds(150));}});producer.join();consumer.join();return 0; }
5. 重要注意事項和最佳實踐
虛假喚醒 (Spurious Wakeup):線程可能在沒有收到通知的情況下被喚醒,因此必須使用謂詞檢查條件。
優先使用
std::lock_guard
和std::unique_lock
:避免手動調用lock()
/unlock()
。在通知前解鎖:在調用
notify_one()
或notify_all()
前解鎖,可以讓被喚醒的線程立即獲取鎖,提高性能。使用 RAII:確保異常安全,所有資源都能正確釋放。
避免嵌套鎖:容易導致死鎖。
總結對比
特性 | 互斥鎖 (Mutex) | 條件變量 (Condition Variable) |
---|---|---|
主要目的 | 保護共享數據,避免數據競爭 | 線程間通信和協調 |
操作 | lock() , unlock() | wait() , notify_one() , notify_all() |
配合使用 | 可以單獨使用 | 必須與互斥鎖配合使用 |
阻塞原因 | 等待獲取鎖 | 等待某個條件成立 |
典型模式 | 臨界區保護 | 生產者-消費者 |
掌握互斥鎖和條件變量是編寫正確、高效多線程 C++ 程序的基礎。