C++ 中的 std::mutex
與多線程同步
在多線程編程中,互斥鎖(Mutex) 是一種同步機制,用于保護共享資源(如變量、數據結構)免受數據競爭(Data Race)的影響。C++ 標準庫中的 std::mutex
提供了基本的互斥功能,常與 std::lock_guard
或 std::unique_lock
配合使用,以實現線程安全的資源訪問。
1. std::mutex
的基本概念
- 作用:確保在同一時刻只有一個線程可以訪問共享資源。
- 頭文件:
#include <mutex>
- 常用方法:
lock()
:鎖定互斥鎖,若已被鎖定則阻塞等待。unlock()
:釋放互斥鎖。try_lock()
:嘗試鎖定互斥鎖,若失敗則立即返回(不阻塞)。
2. 示例:不使用互斥鎖導致數據競爭
場景:多個線程對共享計數器進行遞增操作。
#include <iostream>
#include <thread>
#include <vector>int counter = 0;void increment() {for (int i = 0; i < 100000; ++i) {++counter; // 非原子操作,存在數據競爭}
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 10; ++i) {threads.emplace_back(increment);}for (auto& t : threads) {t.join();}std::cout << "Final counter value: " << counter << std::endl;return 0;
}
輸出(每次運行結果可能不同):
Final counter value: 789123 // 錯誤值(期望 1,000,000)
問題分析:
++counter
不是原子操作,多個線程同時修改counter
導致數據競爭。- 結果不可預測,可能遠小于預期值。
3. 使用 std::mutex
和 std::lock_guard
修復數據競爭
改進方案:通過互斥鎖保護共享資源。
#include <iostream>
#include <thread>
#include <vector>
#include <mutex> // 引入 mutexint counter = 0;
std::mutex mtx; // 定義互斥鎖void increment() {for (int i = 0; i < 100000; ++i) {mtx.lock(); // 加鎖++counter; // 安全訪問共享資源mtx.unlock(); // 解鎖}
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 10; ++i) {threads.emplace_back(increment);}for (auto& t : threads) {t.join();}std::cout << "Final counter value: " << counter << std::endl;return 0;
}
輸出:
Final counter value: 1000000
關鍵點:
- 互斥鎖保護臨界區:
mtx.lock()
和mtx.unlock()
之間的代碼為臨界區,確保一次只有一個線程執行。 - 避免死鎖:必須成對調用
lock()
和unlock()
,否則可能導致死鎖。
4. 使用 std::lock_guard
自動管理鎖的生命周期
改進方案:使用 RAII(Resource Acquisition Is Initialization) 模式自動加鎖/解鎖。
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>int counter = 0;
std::mutex mtx;void increment() {for (int i = 0; i < 100000; ++i) {std::lock_guard<std::mutex> lock(mtx); // 構造時加鎖,析構時自動解鎖++counter; // 安全訪問共享資源}
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 10; ++i) {threads.emplace_back(increment);}for (auto& t : threads) {t.join();}std::cout << "Final counter value: " << counter << std::endl;return 0;
}
輸出:
Final counter value: 1000000
優勢:
- 自動解鎖:
lock_guard
在作用域結束時自動調用unlock()
,避免手動管理鎖。 - 異常安全:即使發生異常,
lock_guard
也會確保解鎖。
5. std::unique_lock
:更靈活的鎖管理
使用場景:需要延遲鎖定、條件變量或動態鎖定策略。
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;
int shared_data = 0;void access_data() {std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 不立即加鎖// 做一些不需要鎖的操作...lock.lock(); // 顯式加鎖shared_data = 42;std::cout << "Data updated: " << shared_data << std::endl;// lock 在作用域結束時自動解鎖
}int main() {std::thread t(access_data);t.join();return 0;
}
輸出:
Data updated: 42
特點:
- 延遲鎖定:使用
std::defer_lock
參數延遲加鎖。 - 支持移動:可以將鎖的所有權轉移到其他線程。
- 配合條件變量:常用于
std::condition_variable
的等待操作。
6. 互斥鎖的最佳實踐
原則 | 說明 |
---|---|
保護共享資源 | 所有對共享資源的訪問都必須通過互斥鎖保護。 |
最小化臨界區 | 鎖定時間越短越好,避免阻塞其他線程。 |
避免死鎖 | 按固定順序加鎖,不嵌套加鎖。 |
使用 RAII 風格 | 優先使用 lock_guard 或 unique_lock ,避免手動調用 lock() /unlock() 。 |
避免遞歸加鎖 | 默認 std::mutex 不支持遞歸加鎖,多次調用 lock() 會導致死鎖。 |
7. 總結
- 互斥鎖(
std::mutex
) 是 C++ 多線程編程中保護共享資源的核心工具。 std::lock_guard
提供自動加鎖/解鎖,適合大多數同步場景。std::unique_lock
提供更靈活的鎖管理,適用于復雜同步需求(如條件變量)。- 關鍵點:確保所有共享資源訪問都通過互斥鎖保護,避免數據競爭和死鎖。
通過合理使用互斥鎖,可以編寫出高效、安全的多線程程序。