跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

C++ 多线程同步:互斥锁 mutex 实战指南

综述由AI生成C++ 多线程编程中,多个线程同时访问共享资源会引发数据竞争,导致结果不可预测。本文通过实例演示如何利用 std::mutex 和 std::lock_guard 解决资源竞争问题。重点讲解了互斥锁的基本接口、RAII 机制下的自动锁管理、死锁产生的四个必要条件及其规避策略,如固定锁获取顺序和使用 std::lock。最后通过多线程售票系统的实战案例,展示了互斥锁在实际场景中的应用,确保并发环境下的数据一致性与安全性。

PhpPioneer发布于 2026/3/24更新于 2026/5/33 浏览
C++ 多线程同步:互斥锁 mutex 实战指南

C++ 多线程同步:互斥锁 mutex 实战指南

在多线程编程中,当多个线程同时访问共享资源时,资源竞争几乎是不可避免的。比如两个线程同时对同一个变量进行读写操作,最终结果往往与预期不符。这就是典型的线程安全问题,而解决它的核心手段就是线程同步。

⚠️ 注意:如果线程不同步,数据竞争会导致程序运行结果不可预测,严重时甚至引发崩溃。

多线程同步的必要性

举个简单的反例,两个线程同时对全局变量 count 进行自增操作:

#include <iostream>
#include <thread>

using namespace std;

int count = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        count++; // 非原子操作,存在数据竞争
    }
}

int main() {
    thread t1(increment);
    thread t2(increment);
    t1.join();
    t2.join();
    cout << "最终 count 值:" << count << endl;
    return 0;
}

运行该程序会发现,最终 count 的值大概率小于 200000。这是因为 count++ 并非原子操作,被两个线程交替执行打乱了步骤。

C++ 标准库中的互斥锁

C++11 及以后的标准库提供了 <mutex> 头文件,封装了多种互斥锁相关的类。最基础且常用的就是 std::mutex。

std::mutex 的核心接口
  • lock():获取互斥锁。如果锁已被其他线程占用,当前线程会阻塞等待。
  • unlock():释放互斥锁。必须与 lock() 成对使用。
  • try_lock():尝试获取互斥锁。如果获取失败,不会阻塞,直接返回 false。
  • std::lock_guard:自动管理锁的生命周期

    直接使用 lock() 和 unlock() 容易出现遗漏解锁的情况。比如程序抛出异常时,unlock() 可能无法执行,导致死锁。std::lock_guard 基于 RAII 机制实现,可以自动在构造时加锁,析构时解锁。

    实际开发中优先使用 std::lock_guard,而非手动调用 lock()/unlock()。

    互斥锁实战:解决数据竞争问题

    我们使用 std::mutex 和 std::lock_guard 改造上面的反例:

    #include <iostream>
    #include <thread>
    #include <mutex>
    
    using namespace std;
    
    int count = 0;
    mutex mtx; // 定义全局互斥锁
    
    void increment() {
        for (int i = 0; i < 100000; ++i) {
            lock_guard<mutex> lock(mtx); // 自动加锁
            count++; // 临界区代码,此时只有一个线程能执行
        } // lock_guard 析构,自动解锁
    }
    
    int main() {
        thread t1(increment);
        thread t2(increment);
        t1.join();
        t2.join();
        cout << "最终 count 值:" << count << endl;
        return 0;
    }
    

    运行该程序,最终 count 的值稳定等于 200000。这说明互斥锁成功保护了临界区代码,避免了数据竞争。

    这里涉及两个关键概念:

    1. 临界区:需要被保护的、不能被多个线程同时执行的代码段。上例中 count++ 就是临界区。
    2. 互斥锁的作用:保证同一时刻只有一个线程能进入临界区。

    死锁的产生与规避

    死锁是指多个线程互相持有对方需要的锁,导致所有线程都无法继续执行的状态。

    死锁的四个必要条件
    1. 互斥条件:资源只能被一个线程占用。
    2. 请求与保持条件:线程持有一个资源的同时,请求其他线程持有的资源。
    3. 不可剥夺条件:线程已持有的资源不能被其他线程强制夺走。
    4. 循环等待条件:多个线程形成首尾相接的循环等待资源关系。
    死锁的示例

    两个线程分别持有一个锁,同时请求对方的锁:

    #include <iostream>
    #include <thread>
    #include <mutex>
    
    using namespace std;
    
    mutex mtx1, mtx2;
    
    void thread1() {
        mtx1.lock();
        this_thread::sleep_for(chrono::milliseconds(100)); // 确保 thread2 先拿到 mtx2
        mtx2.lock(); // 等待 mtx2,此时 thread2 持有 mtx2 并等待 mtx1
        cout << "thread1 执行完毕" << endl;
        mtx2.unlock();
        mtx1.unlock();
    }
    
    void thread2() {
        mtx2.lock();
        this_thread::sleep_for(chrono::milliseconds(100)); // 确保 thread1 先拿到 mtx1
        mtx1.lock(); // 等待 mtx1,此时 thread1 持有 mtx1 并等待 mtx2
        cout << "thread2 执行完毕" << endl;
        mtx1.unlock();
        mtx2.unlock();
    }
    
    int main() {
        thread t1(thread1);
        thread t2(thread2);
        t1.join();
        t2.join();
        return 0;
    }
    

    运行该程序,两个线程会互相等待,陷入死锁状态,无法输出任何内容。

    规避死锁的常用方法
    1. 固定锁的获取顺序:所有线程按照相同的顺序获取锁。比如上例中,让两个线程都先获取 mtx1,再获取 mtx2。
    2. 使用 std::lock 同时获取多个锁:std::lock 可以一次性获取多个互斥锁,避免循环等待。
    3. 使用带超时的锁尝试:通过 try_lock() 或 std::timed_mutex,在超时后放弃获取锁,避免永久阻塞。

    实战案例:多线程售票系统

    模拟一个售票系统,多个窗口同时售票,使用互斥锁保证票数不会出现负数或重复售票的情况。

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <vector>
    
    using namespace std;
    
    int tickets = 100; // 总票数
    mutex mtx;         // 售票函数
    
    void sell_tickets(int window_id) {
        while (true) {
            lock_guard<mutex> lock(mtx);
            if (tickets > 0) {
                cout << "窗口" << window_id << "售出第" << tickets << "张票" << endl;
                tickets--;
                this_thread::sleep_for(chrono::milliseconds(50)); // 模拟售票耗时
            } else {
                break;
            }
        }
        cout << "窗口" << window_id << "售票结束" << endl;
    }
    
    int main() {
        vector<thread> windows; // 创建 5 个售票窗口
        for (int i = 1; i <= 5; ++i) {
            windows.emplace_back(sell_tickets, i);
        }
        // 等待所有窗口售票结束
        for (auto& t : windows) {
            t.join();
        }
        cout << "所有票已售罄" << endl;
        return 0;
    }
    

    运行效果显示,5 个窗口有序售票,最终票数从 100 递减到 0,不会出现重复售票或票数为负的情况。

    本章小结

    1. 多线程访问共享资源时必须进行同步,否则会出现数据竞争问题。
    2. std::mutex 是 C++ 最基础的互斥锁,搭配 std::lock_guard 可以安全地管理锁的生命周期。
    3. 死锁由四个必要条件引发,通过固定锁顺序、使用 std::lock 等方法可以有效规避。
    4. 互斥锁的核心是保护临界区,确保同一时刻只有一个线程能执行临界区代码。

    目录

    1. C++ 多线程同步:互斥锁 mutex 实战指南
    2. 多线程同步的必要性
    3. C++ 标准库中的互斥锁
    4. std::mutex 的核心接口
    5. std::lock_guard:自动管理锁的生命周期
    6. 互斥锁实战:解决数据竞争问题
    7. 死锁的产生与规避
    8. 死锁的四个必要条件
    9. 死锁的示例
    10. 规避死锁的常用方法
    11. 实战案例:多线程售票系统
    12. 本章小结
    • 💰 8折买阿里云服务器限时8折了解详情
    • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
    • 代充Chatgpt Plus/pro 帐号了解详情
    • 🤖 一键搭建Deepseek满血版了解详情
    • 一键打造专属AI 智能体了解详情
    极客日志微信公众号二维码

    微信扫一扫,关注极客日志

    微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

    更多推荐文章

    查看全部
    • Python 环境下 Gurobi 安装与配置指南
    • AI+ 游戏开发:使用 DeepSeek 构建贪吃蛇游戏
    • Babylon.js 导出器使用指南:构建 Web 3D 场景
    • Creo 二次开发:从 Python 到 C++ 的进阶之路
    • node-llama-cpp 跨平台安装与配置:Windows、Linux、Mac
    • Seedance 2.0 双分支扩散变换器架构解析与工程实现
    • VirtualBox Ubuntu 虚拟机与 Windows 主机文本复制粘贴设置指南
    • 4nm 移动 SoC 三星高能效 NPU 架构解析
    • 算法实战:Z 字形变换与外观数列详解
    • C++ 面试核心知识点总结:语法、内存与类机制
    • Ubuntu 安装 OpenClaw 遇 Gateway 服务检查失败问题排查与解决
    • 前端基础入门:HTML、CSS 与 JavaScript 核心概览
    • Flutter 与 Web 混合开发实践
    • DICOM 标准详解:文件解析、Java/Python 库与 AI 应用
    • Neo4j Desktop 2 安装与使用指南
    • 2026 前端跨端框架选型指南
    • 前端加密靶场 encrypt-labs 环境搭建与全关卡解析
    • 基于 Python 的 Live2D 虚拟主播软件
    • 基于 Go 构建可自我演进 AI 助手的架构设计与实践
    • Mac Mini 本地部署 OpenClaw 智能体实战指南

    相关免费在线工具

    • 加密/解密文本

      使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

    • Gemini 图片去水印

      基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

    • Base64 字符串编码/解码

      将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

    • Base64 文件转换器

      将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

    • Markdown转HTML

      将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

    • HTML转Markdown

      将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online