news 2026/2/5 20:46:39

多线程锁基础

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多线程锁基础

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

      • 先理解核心:为什么需要锁?
    • 一、最基础的锁:std::mutex(互斥锁)
      • 1. 作用
      • 2. 核心用法
      • 3. 适用场景
      • 4. 代码示例(基础用法,仅演示原理)
    • 二、最安全的锁:std::lock_guard(RAII封装的mutex)
      • 1. 作用
      • 2. 核心用法
      • 3. 适用场景
      • 4. 代码示例(替代手动lock/unlock)
    • 三、更灵活的锁:std::unique_lock
      • 1. 作用
      • 2. 核心用法
      • 3. 适用场景
      • 4. 代码示例(灵活解锁)
    • 四、递归场景用:std::recursive_mutex(递归互斥锁)
      • 1. 作用
      • 2. 核心用法
      • 3. 适用场景
      • 4. 代码示例(递归加锁)
    • 五、不想等太久:std::timed_mutex(带超时的互斥锁)
      • 1. 作用
      • 2. 核心用法
      • 3. 适用场景
      • 4. 代码示例(超时加锁)
    • 六、读多写少用:std::shared_mutex(共享互斥锁/C++17)
      • 1. 作用
      • 2. 核心用法
      • 3. 适用场景
      • 4. 代码示例(读写分离)
    • 总结:核心知识点回顾

你想从零开始学习C++多线程中的锁,希望我用基础、简单的方式讲解C++里有哪些锁、它们的使用方法,以及不同场景该选择哪种锁。我会从最基础的概念讲起,用简单的代码示例帮你理解,避免复杂术语。

先理解核心:为什么需要锁?

多线程程序中,多个线程同时访问共享资源(比如一个全局变量、一个结构体)时,会出现“数据竞争”问题。比如两个线程同时给count++,最终结果可能不对。锁的作用就是保证同一时间只有一个(或指定数量的)线程能访问共享资源,避免数据错乱。

C++11及以后的标准库(<mutex>头文件)提供了多种锁,下面按“从简单到复杂”的顺序讲解,重点讲基础且常用的类型。


一、最基础的锁:std::mutex(互斥锁)

1. 作用

最核心、最基础的独占锁:同一时间只能有一个线程锁定它,其他线程尝试锁定会阻塞(一直等),直到锁被释放。

2. 核心用法

  • lock():加锁(如果锁已被占用,当前线程阻塞)
  • unlock():解锁(必须和lock配对,否则会导致死锁/资源泄漏)

3. 适用场景

最简单的独占式访问共享资源(比如单线程写、其他线程等),但不推荐手动用lock/unlock(容易忘unlock)。

4. 代码示例(基础用法,仅演示原理)

#include<iostream>#include<thread>#include<mutex>// 共享资源intcount=0;// 全局互斥锁std::mutex mtx;// 线程函数:累加countvoidadd_count(){for(inti=0;i<10000;++i){// 加锁:同一时间只有一个线程能执行下面的代码mtx.lock();count++;// 临界区(访问共享资源的代码)// 解锁:必须手动解锁,否则其他线程永远拿不到锁mtx.unlock();}}intmain(){std::threadt1(add_count);std::threadt2(add_count);t1.join();t2.join();std::cout<<"最终count值:"<<count<<std::endl;// 正确结果应该是20000return0;}

⚠️ 注意:如果代码在lock后抛出异常,unlock不会执行,会导致死锁!所以实际开发不用这种方式。


二、最安全的锁:std::lock_guard(RAII封装的mutex)

1. 作用

基于“RAII(资源获取即初始化)”思想,自动加锁、自动解锁:创建lock_guard对象时自动调用lock(),对象销毁(比如出作用域)时自动调用unlock(),彻底避免忘解锁的问题。

2. 核心用法

直接创建lock_guard对象,传入mutex即可,无需手动调用lock/unlock。

3. 适用场景

90%的简单独占访问场景(比如单次读写共享资源),是实际开发中最常用的锁。

4. 代码示例(替代手动lock/unlock)

#include<iostream>#include<thread>#include<mutex>intcount=0;std::mutex mtx;voidadd_count(){for(inti=0;i<10000;++i){// 创建lock_guard,自动加锁std::lock_guard<std::mutex>lock(mtx);count++;// 临界区// 出作用域时,lock对象销毁,自动解锁(即使count++抛异常也会解锁)}}intmain(){std::threadt1(add_count);std::threadt2(add_count);t1.join();t2.join();std::cout<<"最终count值:"<<count<<std::endl;// 20000return0;}

三、更灵活的锁:std::unique_lock

1. 作用

lock_guard灵活:可以手动控制加锁/解锁时机、支持超时加锁、可以配合条件变量(std::condition_variable)使用。

2. 核心用法

  • 构造时可选是否立即加锁:std::unique_lock<std::mutex> lock(mtx, std::defer_lock);(defer_lock表示延迟加锁)
  • 手动加锁:lock.lock()
  • 手动解锁:lock.unlock()

3. 适用场景

  • 需要在临界区中间临时解锁(比如锁内要调用耗时的无锁函数)
  • 配合条件变量(多线程通信的核心场景)
  • 需要超时尝试加锁(后续讲timed_mutex时会结合)

4. 代码示例(灵活解锁)

#include<iostream>#include<thread>#include<mutex>#include<chrono>intcount=0;std::mutex mtx;voidadd_count(){for(inti=0;i<10000;++i){// 延迟加锁(构造时不加锁)std::unique_lock<std::mutex>lock(mtx,std::defer_lock);// 手动加锁lock.lock();count++;// 临时解锁(比如要执行耗时操作,不占用锁)lock.unlock();// 模拟耗时操作(无需锁的逻辑)std::this_thread::sleep_for(std::chrono::microseconds(1));// 再次加锁(如果需要)lock.lock();count++;// 出作用域自动解锁}}intmain(){std::threadt1(add_count);std::threadt2(add_count);t1.join();t2.join();std::cout<<"最终count值:"<<count<<std::endl;// 40000return0;}

四、递归场景用:std::recursive_mutex(递归互斥锁)

1. 作用

允许同一个线程多次锁定同一个锁(普通mutex如果同一个线程多次lock会直接死锁)。

2. 核心用法

mutex完全一致,但解锁次数必须和加锁次数相同。

3. 适用场景

  • 函数递归调用时需要加锁(比如递归遍历树,要保护共享的节点计数)
  • 同一个线程可能多次获取同一个锁的场景(尽量少用,大概率是代码设计有问题)

4. 代码示例(递归加锁)

#include<iostream>#include<mutex>std::recursive_mutex rmtx;intsum=0;// 递归函数:计算1~n的和voidrecursive_add(intn){// 加锁(同一线程多次调用也不会死锁)rmtx.lock();if(n==0){rmtx.unlock();// 递归出口,解锁return;}sum+=n;recursive_add(n-1);// 递归调用,再次加锁rmtx.unlock();// 解锁次数和加锁次数一致}intmain(){recursive_add(10);std::cout<<"1~10的和:"<<sum<<std::endl;// 55return0;}

五、不想等太久:std::timed_mutex(带超时的互斥锁)

1. 作用

尝试加锁时可以设置超时时间,超时后不再阻塞,返回false(普通mutex会无限期等)。

2. 核心用法

  • try_lock_for(时间段):尝试加锁,超时返回false(比如等1秒)
  • try_lock_until(时间点):尝试加锁到指定时间点,超时返回false

3. 适用场景

不想让线程无限期等待锁(比如非阻塞式访问资源,超时后可以做其他逻辑,比如提示“资源忙”)。

4. 代码示例(超时加锁)

#include<iostream>#include<thread>#include<mutex>#include<chrono>std::timed_mutex tmtx;voidtry_lock_with_timeout(){// 尝试加锁,最多等1秒if(tmtx.try_lock_for(std::chrono::seconds(1))){std::cout<<"线程"<<std::this_thread::get_id()<<"获取锁成功!"<<std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));// 占用锁2秒tmtx.unlock();}else{std::cout<<"线程"<<std::this_thread::get_id()<<"获取锁超时!"<<std::endl;}}intmain(){std::threadt1(try_lock_with_timeout);std::threadt2(try_lock_with_timeout);t1.join();t2.join();return0;}

输出(t1拿到锁,t2等1秒超时):

线程140709289268928获取锁成功! 线程140709280876224获取锁超时!

六、读多写少用:std::shared_mutex(共享互斥锁/C++17)

1. 作用

区分“读锁”和“写锁”,提升并发效率:

  • 读锁(共享锁):多个线程可以同时加读锁(读-读不互斥)
  • 写锁(独占锁):只有一个线程能加写锁(读-写、写-写互斥)

2. 核心用法

  • 读锁:std::shared_lock<std::shared_mutex>
  • 写锁:std::unique_lock<std::shared_mutex>

3. 适用场景

读多写少的场景(比如配置文件读取、缓存查询、日志读取),比普通mutex并发更高。

4. 代码示例(读写分离)

#include<iostream>#include<thread>#include<mutex>#include<shared_mutex>// C++17及以上// 共享资源:模拟配置信息std::string config="初始配置";// 共享互斥锁std::shared_mutex smtx;// 读配置(多线程同时读)voidread_config(intid){// 加读锁(共享锁)std::shared_lock<std::shared_mutex>lock(smtx);std::cout<<"读者"<<id<<"读取配置:"<<config<<std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));// 模拟读耗时}// 写配置(独占)voidwrite_config(conststd::string&new_config){// 加写锁(独占锁)std::unique_lock<std::shared_mutex>lock(smtx);std::cout<<"写者修改配置:"<<new_config<<std::endl;config=new_config;std::this_thread::sleep_for(std::chrono::milliseconds(200));// 模拟写耗时}intmain(){// 5个读者线程 + 1个写者线程std::threadt1(read_config,1);std::threadt2(read_config,2);std::threadt3(write_config,"新配置1");std::threadt4(read_config,3);std::threadt5(read_config,4);t1.join();t2.join();t3.join();t4.join();t5.join();return0;}

输出(读者1、2同时读,写者执行时读者3、4等待,写完成后读者3、4读新配置):

读者1读取配置:初始配置 读者2读取配置:初始配置 写者修改配置:新配置1 读者3读取配置:新配置1 读者4读取配置:新配置1

总结:核心知识点回顾

  1. 基础首选std::lock_guard(RAII自动管理,简单安全,90%场景够用),底层依赖std::mutex
  2. 灵活场景std::unique_lock(手动控制锁的生命周期、配合条件变量)。
  3. 特殊场景
    • 递归调用加锁:std::recursive_mutex(尽量少用);
    • 不想无限等锁:std::timed_mutex(超时加锁);
    • 读多写少:std::shared_mutex(C++17,提升读并发)。
  4. 核心原则:永远用RAII方式(lock_guard/unique_lock),不要手动lock/unlock,避免死锁。

新手入门先掌握std::mutex+std::lock_guard即可,这是最基础、最常用的组合,后续再逐步学习unique_lock和共享锁。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/3 17:27:55

基于Thinkphp和Laravel的乡村政务举报投诉办公系统的设计与实现_

目录 系统背景技术架构核心功能模块创新点实现效果 项目开发技术介绍PHP核心代码部分展示系统结论源码获取/同行可拿货,招校园代理 系统背景 乡村政务举报投诉办公系统旨在利用现代化信息技术提升基层政务处理效率&#xff0c;解决传统投诉渠道响应慢、流程不透明等问题。该系…

作者头像 李华
网站建设 2026/2/1 19:57:26

基于Thinkphp和Laravel的喀什旅游网站酒店机票美食_hw31x_

目录 开发框架选择功能模块设计技术实现要点喀什特色整合部署与运维 项目开发技术介绍PHP核心代码部分展示系统结论源码获取/同行可拿货,招校园代理 开发框架选择 ThinkPHP和Laravel均为成熟的PHP框架。ThinkPHP适合快速开发&#xff0c;中文文档丰富&#xff1b;Laravel生态完…

作者头像 李华
网站建设 2026/1/28 22:02:38

基于Thinkphp和Laravel的被裁人员就业求职招聘管理系统_w3209_

目录系统概述技术栈核心功能项目亮点适用场景项目开发技术介绍PHP核心代码部分展示系统结论源码获取/同行可拿货,招校园代理系统概述 Thinkphp和Laravel框架结合开发的被裁人员就业求职招聘管理系统&#xff08;项目代号_w3209_&#xff09;旨在为被裁人员提供职业匹配、岗位推…

作者头像 李华
网站建设 2026/1/30 7:16:58

基于Thinkphp和Laravel的高校电动车租赁系统_hb0fi_

目录 系统概述技术架构核心功能创新点部署与扩展 项目开发技术介绍PHP核心代码部分展示系统结论源码获取/同行可拿货,招校园代理 系统概述 该系统基于ThinkPHP和Laravel框架开发&#xff0c;旨在为高校提供电动车租赁管理解决方案&#xff0c;涵盖用户管理、车辆调度、订单处理…

作者头像 李华
网站建设 2026/2/5 13:15:21

9款AI写论文哪个好?实测后锁定宏智树AI:文献真实、数据可溯,毕业论文一键通关!官网www.hzsxueshu.com 微信公众号搜一搜宏智树AI

公众号搜一搜宏智树 AI 作为深耕论文写作科普的教育测评博主&#xff0c;每年毕业季都要收到上百条 “AI 写论文选哪个” 的求助。为了给大家一份实打实的参考&#xff0c;我耗时 1 个半月&#xff0c;以《乡村振兴背景下农村电商物流效率提升路径研究》为统一课题&#xff0c;…

作者头像 李华
网站建设 2026/1/30 5:36:37

从 YOLOv5n 到 OpenVINO INT8 ≤2MB一个课堂手机检测系统的工程化落地实践

一、为什么“课堂手机检测”不是一个简单问题&#xff1f;在很多人眼里&#xff0c;“手机检测”似乎只是一个目标检测任务&#xff1a;数据集 YOLO → 训练 → 结束。但当项目目标从“能跑”升级为 “能部署、能交付、能在真实课堂环境稳定运行” 时&#xff0c;问题的难度会…

作者头像 李华