C++ 多线程同步之互斥锁(mutex)实战

C++ 多线程同步之互斥锁(mutex)实战

C++ 多线程同步之互斥锁(mutex)实战

在这里插入图片描述

💡 学习目标:掌握 C++ 标准库中互斥锁的基本用法,理解多线程同步的核心原理,能够解决多线程环境下的资源竞争问题。
💡 学习重点std::mutexstd::lock_guard 的使用、死锁的产生原因及规避方法、实际场景中的同步案例实现。

48.1 多线程同步的必要性

在多线程编程中,当多个线程同时访问共享资源时,会出现资源竞争问题。
例如两个线程同时对同一个变量进行读写操作,会导致最终结果与预期不符。
这种问题被称为线程安全问题,而解决该问题的核心就是线程同步

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

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

#include<iostream>#include<thread>usingnamespace std;int count =0;voidincrement(){for(int i =0; i <100000;++i){ count++;// 非原子操作,存在数据竞争}}intmain(){ thread t1(increment); thread t2(increment); t1.join(); t2.join(); cout <<"最终 count 值:"<< count << endl;return0;}

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

48.2 C++ 标准库中的互斥锁

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

48.2.1 std::mutex 的核心接口

  • lock():获取互斥锁。如果锁已被其他线程占用,当前线程会阻塞等待。
  • unlock():释放互斥锁。必须与 lock() 成对使用。
  • try_lock():尝试获取互斥锁。如果获取失败,不会阻塞,直接返回 false

48.2.2 std::lock_guard:自动管理锁的生命周期

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

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

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

我们使用 std::mutexstd::lock_guard 改造 48.1 节的反例:

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

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

48.3.1 关键概念解释

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

48.4 死锁的产生与规避

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

48.4.1 死锁的四个必要条件

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

48.4.2 死锁的示例

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

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

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

48.4.3 规避死锁的常用方法

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

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

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

#include<iostream>#include<thread>#include<mutex>#include<vector>usingnamespace std;int tickets =100;// 总票数 mutex mtx;// 售票函数voidsell_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;}intmain(){ 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;return0;}

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

48.6 本章小结

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

Read more

《MySQL 权限与访问进阶:普通用户搭建、跨端登录及 C/C++ 开发对接教程》

《MySQL 权限与访问进阶:普通用户搭建、跨端登录及 C/C++ 开发对接教程》

前引:在 MySQL 开发与运维中,普通用户的创建与权限管控是保障数据库安全的基础,而本地连接、远程访问的配置,以及 C/C++ 程序的对接调用,则是打通 “数据库 - 应用” 链路的核心环节。很多开发者在实际操作中会遇到 “用户创建后登录失败”“远程连接被拒绝”“C/C++ 接口调用报错” 等问题,本文将从实战出发,一步步拆解 MySQL 普通用户的创建配置、本地 / 远程登录的关键步骤,以及 C/C++ 访问 MySQL 的完整流程(含环境搭建、代码实现、常见问题排查),帮助开发者快速搞定多场景下的 MySQL 访问需求! 目录 【一】普通用户的创建 (1)查看user表 (2)创建普通用户 (3)删除普通用户

By Ne0inhk
C++ 多态:面向对象的动态行为核心机制

C++ 多态:面向对象的动态行为核心机制

C++ 多态:面向对象的动态行为核心机制 💡 学习目标:掌握多态的概念与分类,理解虚函数的作用原理,能够熟练使用多态实现程序的动态行为扩展。 💡 学习重点:静态多态与动态多态的区别、虚函数的定义与使用、纯虚函数与抽象类、多态的实战应用场景。 一、多态的概念与分类 ✅ 结论:多态是 C++ 面向对象三大特性之一,指同一行为在不同对象上表现出不同的形态,核心是“一个接口,多种实现”。 多态主要分为两大类,二者的实现原理和触发时机截然不同: 1. 静态多态:编译阶段确定调用关系,也叫编译时多态,实现方式包括函数重载和运算符重载 2. 动态多态:运行阶段确定调用关系,也叫运行时多态,实现方式是虚函数 + 基类指针/引用 生活中的多态示例:同样是“动物叫”这个行为,猫的叫声是“喵喵喵”,狗的叫声是“汪汪汪”,不同动物对象表现出不同的行为形态。 二、静态多态:编译时确定的多态性 💡 静态多态的调用关系在编译阶段就已确定,编译器会根据参数列表的差异匹配对应的函数。

By Ne0inhk
飞算JavaAI需求转SpringBoot项目沉浸式体验

飞算JavaAI需求转SpringBoot项目沉浸式体验

文章目录 * 一、引言:从手撸代码到智能开发的蜕变 * 二、智能引导:六步实现需求到代码的无缝转换 * 1. 需求精准解析 * 2. 接口智能设计 * 3. 表结构可视化设计 * 4. 业务逻辑编排 * 5. 代码预览与确认 * 6. 一键生成可运行工程(图6) * 三、效率与质量的双重跃升:数据见证变革 * 1. 开发效率对比 * 2. 代码质量对比 * 3. 性能表现 * 四、与同类产品的差异化优势 * 1. 与Cursor的对比 * 2. 与通义灵码的对比 * 3. 与传统低代码平台的对比 * 五、结语:重构Java开发的未来图景 一、引言:从手撸代码到智能开发的蜕变 作为一名深耕Java开发多年的工程师,我曾无数次在需求变更、代码重构的泥潭中挣扎。传统开发模式下,从需求分析到Spring Boot项目落地,往往需要耗费数周时间,

By Ne0inhk
JDK 1.8安装教程(附百度网盘下载地址)

JDK 1.8安装教程(附百度网盘下载地址)

Java开发环境的搭建是学习Java编程的第一步,而JDK 1.8(Java 8)作为长期支持版本(LTS),因其稳定性和广泛兼容性,至今仍是企业和开发者的首选。本文将手把手教你如何在Windows系统中安装和配置JDK 1.8,并附官方下载与网盘备用地址。 一、JDK 1.8下载 1. 官方下载(推荐) 1. 访问Oracle官网下载页面: Oracle JDK 8下载地址 注意:需注册Oracle账号并登录后才能下载。 2. 根据系统选择对应版本: * Windows系统选择 jdk-8uXXX-windows-x64.exe(XXX为版本号) * macOS系统选择 jdk-8uXXX-macosx-x64.dmg 2. 百度网盘备份(备选) 若官网下载困难,可使用以下网盘地址: 链接: https://pan.baidu.com/s/1ov7bWVxu82Bs7FaPSB4DkQ?pwd=

By Ne0inhk