第 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

常見錯誤

  1. 多個線程同時修改共享數據,卻沒有用 mutexatomic 保護。
  2. 手動 lock() 後忘記 unlock(),或者中途異常導致無法解鎖。
  3. 持有鎖時做耗時操作,比如睡眠、網絡請求、文件 IO。
  4. 鎖保護的範圍太小,讀寫共享數據的某些路徑漏掉了鎖。

小結

  • 共享數據需要同步保護。
  • std::mutex 提供互斥訪問。
  • std::lock_guard 是 RAII 鎖,構造加鎖,析構解鎖。
  • 鎖的作用域越小越好,但必須完整覆蓋共享數據訪問。
音乐页