第 18.19.2 節
mutex 與 lock_guard
0瀏覽次數0訪問次數--跳出率--平均停留
本節解決什麼問題
多個線程同時修改同一個變量時,++counter 並不是一個不可分割的動作。它通常包含讀取、加一、寫回幾個步驟。兩個線程交錯執行時,就會產生數據競爭。
保護共享數據最常見的做法是:用 std::mutex 互斥鎖,加鎖後只有一個線程能進入臨界區。std::lock_guard 是 RAII 鎖,構造時加鎖,離開作用域時自動解鎖。
示例代碼
示例 1:用 mutex 和 lock_guard 保護共享計數器
#include <iostream>
#include <mutex>
#include <thread>
int counter = 0;
// 加锁用来保护共享数据,避免多个线程同时修改造成数据竞争。
std::mutex counter_mutex;
void add_many(int times)
{
for (int i = 0; i < times; ++i)
{
std::lock_guard<std::mutex> lock(counter_mutex);
++counter;
}
}
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
// 创建子线程,让这部分代码和 main 线程并发运行。
std::thread t1(add_many, 100000);
std::thread t2(add_many, 100000);
// join 会等待子线程结束,避免 main 提前退出。
t1.join();
t2.join();
std::cout << "counter = " << counter << "\n";
std::cout << "expected = 200000\n";
return 0;
}
運行結果:
counter = 200000
expected = 200000
示例 2:鎖的作用域要儘量小
這個例子裡,線程先在鎖外準備數據,只有更新共享總數時才加鎖。這樣另一個線程不用在無關工作上等待。
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
int total = 0;
// 加锁用来保护共享数据,避免多个线程同时修改造成数据竞争。
std::mutex total_mutex;
int sum_part(const std::vector<int>& data, int begin, int end)
{
int local_sum = 0;
for (int i = begin; i < end; ++i)
{
local_sum += data[i];
}
return local_sum;
}
void worker(const std::vector<int>& data, int begin, int end)
{
int local_sum = sum_part(data, begin, end);
{
std::lock_guard<std::mutex> lock(total_mutex);
total += local_sum;
}
}
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
std::vector<int> data = {1, 2, 3, 4, 5, 6};
// 创建子线程,让这部分代码和 main 线程并发运行。
std::thread t1(worker, std::cref(data), 0, 3);
std::thread t2(worker, std::cref(data), 3, 6);
// join 会等待子线程结束,避免 main 提前退出。
t1.join();
t2.join();
std::cout << "total = " << total << "\n";
return 0;
}
運行結果:
total = 21
lock_guard 和 unique_lock 的區別
| 鎖類型 | 特點 | 常見用途 |
|---|---|---|
std::lock_guard | 簡單,構造加鎖,析構解鎖,不能手動解鎖 | 保護一小段共享數據訪問 |
std::unique_lock | 可延遲加鎖、手動解鎖、轉移所有權 | 條件變量、需要提前釋放鎖的場景 |
初學時優先用 lock_guard。只有需要配合 condition_variable 或者需要更靈活地控制加鎖時,再用 unique_lock。
常見錯誤
- 多個線程同時修改共享數據,卻沒有用
mutex或atomic保護。 - 手動
lock()後忘記unlock(),或者中途異常導致無法解鎖。 - 持有鎖時做耗時操作,比如睡眠、網絡請求、文件 IO。
- 鎖保護的範圍太小,讀寫共享數據的某些路徑漏掉了鎖。
小結
- 共享數據需要同步保護。
std::mutex提供互斥訪問。std::lock_guard是 RAII 鎖,構造加鎖,析構解鎖。- 鎖的作用域越小越好,但必須完整覆蓋共享數據訪問。