文章目录

C++ 多线程同步std::mutex与std::lock_guard

发布于 2026-04-15 18:27:58 · 浏览 23 次 · 评论 0 条

在多线程编程中,当多个线程同时访问共享数据时,会导致数据竞争,从而引发程序崩溃或产生错误结果。为了解决这一问题,C++ 标准库提供了 std::mutex(互斥锁)和 std::lock_guard(锁管理器)。本文将详细介绍如何使用这两个工具来实现线程安全。


理解核心概念

std::mutex 是一种同步原语,用于保护共享数据。它像一个令牌,同一时间只允许一个线程持有它。

std::lock_guard 是一个 RAII(资源获取即初始化)风格的包装器。它在创建时自动锁定互斥锁,在销毁时自动解锁。这是管理 std::mutex 的推荐方式,因为它能防止因异常或提前返回导致的死锁。


步骤 1:引入必要的头文件

在使用多线程功能前,必须先包含 C++ 标准库中的相关头文件。

打开你的 C++ 源代码文件,在文件顶部 添加以下代码:

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>

步骤 2:定义共享资源与互斥锁

假设有一个全局变量 counter,多个线程会对它进行自增操作。为了防止冲突,我们需要定义一个 std::mutex 对象来保护它。

声明一个全局整型变量和一个全局互斥锁对象:

int counter = 0;
std::mutex mtx; // 定义互斥锁

步骤 3:使用 std::mutex 手动加锁(不推荐)

虽然 std::mutex 提供了 lock()unlock() 方法,但在实际编码中直接调用它们非常危险。如果 unlock() 前的代码抛出异常,锁将永远不会被释放,导致死锁。

编写一个不安全的线程函数作为反面教材:

void unsafe_increment() {
    mtx.lock();   // 手动加锁
    counter++;
    // 如果这里发生异常,unlock() 不会被执行,导致死锁
    mtx.unlock(); // 手动解锁
}

步骤 4:使用 std::lock_guard 自动管理锁(推荐)

使用 std::lock_guard 可以彻底避免死锁问题。利用 C++ 的对象生命周期机制:当对象离开作用域时,析构函数自动调用 unlock()

修改线程函数,使用 std::lock_guard

void safe_increment() {
    // 构造函数自动调用 mtx.lock()
    std::lock_guard<std::mutex> lock(mtx); 

    // 这里的代码受到保护,同一时间只有一个线程能执行
    counter++;

    // 离开作用域时,lock 对象析构,自动调用 mtx.unlock()
}

步骤 5:理解 lock_guard 的生命周期

为了更直观地理解 std::lock_guard 如何工作,下图展示了其作用域与锁状态的对应关系。

graph TD A["Enter Function Scope"] -->|Create lock_guard| B["Lock Mutex"] B --> C["Critical Section:\nModify Shared Data"] C -->|Normal Exit / Exception| D["End of Scope"] D -->|Destroy lock_guard| E["Unlock Mutex"] E --> F["Thread Continues"]

步骤 6:编写主函数进行测试

创建多个线程并启动它们,观察 std::lock_guard 是否正确保护了数据。

编写 main 函数:

int main() {
    std::vector<std::thread> threads;

    // 创建 10 个线程
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back(safe_increment);
    }

    // 等待所有线程执行完毕
    for (auto& t : threads) {
        t.join();
    }

    // 输出结果
    std::cout << "Final counter value: " << counter << std::endl;

    return 0;
}

步骤 7:编译并运行程序

使用支持 C++11 或更高版本的编译器进行编译。

执行以下命令编译代码(以 g++ 为例):

g++ -std=c++11 -pthread main.cpp -o main

运行生成的可执行文件:

./main

检查输出结果。如果一切正常,屏幕上应显示 Final counter value: 10。如果去掉 std::lock_guard,这个值可能会小于 10,因为发生了数据竞争。


核心机制对比

下表总结了直接使用 std::mutex 与使用 std::lock_guard 的区别。

特性 std::mutex (手动调用) std::lock_guard (RAII)
加锁方式 显式调用 lock() 构造时自动加锁
解锁方式 显式调用 unlock() 析构时自动解锁
异常安全性 不安全(抛异常会导致死锁) 安全(析构函数确保解锁)
代码简洁性 冗长(需记住解锁) 简洁(自动管理)
适用场景 极少数需要精确控制锁时机的场景 绝大多数常规保护共享数据的场景

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文