C++ 多线程入门:创建线程、加锁、计数
前置知识有C语言基础有 g 编译器基础。本人刚好对g 的安装和基本使用发表过相关博客详情请点击 g 从入门到忘记一、为什么需要多线程单线程程序像一个人在干活——干完一件才能干下一件。但现实中很多事是可以同时进行的主线程处理 UI 事件后台线程下载文件服务器主线程等新连接工作线程处理已连接的请求一个程序同时做多件事边读取数据边计算边写结果C11 之前C 没有标准的多线程库得用操作系统的 APILinux 的 pthread、Windows 的 CreateThread不同平台代码还不一样。C11 引入了std::thread一套代码跨平台编译。二、创建线程#includeiostream#includethreadvoidhello(){std::coutHello from thread!\n;}intmain(){std::threadt(hello);// 创建线程执行 hello 函数t.join();// 主线程等子线程t运行完std::coutHello from main!\n;return0;}编译运行记住加-pthread可能这里会有疑问为什么有#include thread还要加-pthread#include thread是编译时告诉编译器std::thread 类的具体结构而-pthread是链接时告诉链接器去链接 pthread 库的代码。std::thread底层封装的是 Linux 的 pthread 库没有-pthread链接阶段会报undefined reference to pthread_create错误——头文件让编译器认识类型链接选项让链接器找到实现。g-stdc17-pthreadmain.cpp-otest./test输出Hello from thread! Hello from main!2.1 用 lambda 创建实际写代码时很少会单独写一个函数传给 thread更常用的是lambda 表达式——把要干的事直接写在创建线程的地方。好处是不用来回翻代码看函数定义在哪、能直接捕获周围的变量。std::threadt([](){std::coutlambda thread\n;});[]意思是捕获外面所有变量的引用这样 lambda 里就能访问 main 里的变量了。但是引用不是万能的lambda 按引用捕获的陷阱intmain(){std::thread t;{inta42;tstd::thread([a](){sleep(3);std::couta;// 3 秒后跑到这里但 a 已经销毁了});}// ← a 已经没了出作用域自动销毁t.join();// 线程t还在跑访问了不存在的 a导致程序崩溃}[]捕获的是引用不是拷贝。如果线程还没跑完变量先销毁了访问它就是悬空引用——效果和野指针类似。怎么避免优先用[]按值捕获把变量拷贝一份给线程不依赖原变量的生命周期如果用[]必须确保线程在变量销毁前结束在}前先join()多线程场景里确保线程比变量活得短这很难控制所以 lambda 开线程时优先用[]按值捕获明确知道线程不会比变量活得长时才用[]。补充std::thread 还支持这些创建方式了解即可lambda 最常用// 方式1仿函数——重载 operator() 使对象可调用structWorker{voidoperator()(){...}};std::threadt(Worker{});// 方式2成员函数classTask{public:voidrun(){...}};Task obj;std::threadt(Task::run,obj);// 第一个参数是成员函数的地址 Task::run// 第二个参数是哪个对象的这个函数obj// 线程里实际执行的是 obj.run()// 方式3std::bindautofstd::bind([](inta,intb){...},42,100);std::threadt(f);// 线程里执行 f() 等价于执行 lambda(42, 100)2.2 传参数voidwork(intn,conststd::strings){std::coutn sstd::endl;}intmain(){std::threadt(work,42,hello);// 传参给 work(42, hello)t.join();}传引用时要加std::ref否则 thread 默认按值复制voidadd(intx){x;}inta0;std::threadt(add,std::ref(a));// 不加 std::ref 编译不过三、线程生命周期——join 和 detach3.1 忘记join和detach…我刚学多线程时总会犯这个错intmain(){std::threadt([]{std::coutwork\n;});// 忘记调用 join和detachreturn0;}std::thread 的析构函数会检查线程是否还可 join。只要没调过 join 也没调过 detach线程就处于可 join状态——不管它跑没跑完。可 join的线程在析构时不会被默默托管C 标准的选择是直接调用 std::terminate() 终止程序然后逼你做出明确选择。对比一下正确的写法intmain(){std::threadt([]{std::coutwork\n;});t.join();// 等它跑完再退出程序正常结束return0;}二选一方式行为场景t.join()主线程阻塞等 t 跑完再继续需要线程的结果或者必须等它运行完t.detach()放手让 t 在后台跑不再关联日志、定时器等 不影响主流程的工作3.2 理解 RAII这里其实涉及 C 一个非常重要的设计思想——RAII资源获取即初始化。作者后续会针对 RAII 机制单独出一篇详细讲解这里先简单了解std::thread本身就是一个 RAII 对象——它管理线程这个资源的生命周期。只是它的设计者选择在你不做决定时直接 terminate逼你必须在 join 和 detach 之间选一个而不是默默帮你 detach 让线程在后台失控。到此你学会了创建线程和管理它的生命周期。但多个线程跑起来之后如果它们要共享数据就会引出新的问题——一个线程正在写、另一个线程同时读结果会怎样下面就来复现这个经典场景。四、经典问题竞态条件4.1 问题复现#includeiostream#includethread#includemutex#includeatomicintcounter0;// 共享变量intmain(){std::threadt1([](){for(inti0;i100000;i)counter;});std::threadt2([](){for(inti0;i100000;i)counter;});std::threadt3([](){for(inti0;i100000;i)counter;});std::threadt4([](){for(inti0;i100000;i)counter;});t1.join();t2.join();t3.join();t4.join();std::cout结果counter (期望400000)std::endl;return0;}我的运行结果结果255517 (期望400000)每次都跑出不同的数永远小于 400000。这就是竞态条件。4.2 为什么——从 CPU 指令看counter不是一条操作而是三步mov eax, [counter] ; ① 把 counter 的值读到寄存器 add eax, 1 ; ② 寄存器 1 mov [counter], eax ; ③ 写回内存线程切换可能发生在任意两步之间。比如线程A: mov eax, [counter] eax100 时间片到A被切走 线程B: mov eax, [counter] eax100还是旧值 线程B: add eax, 1 eax101 线程B: mov [counter], eax counter101 线程A被调度回来 线程A: add eax, 1 eax101用的是之前读的100 线程A: mov [counter], eax counter101两个线程各加一次期望 102实际只加了 1。一次更新被丢失了。用时序图看更直观线程Bcounter(内存)线程A线程Bcounter(内存)线程A时间片到切走切走调度 A① 读 counter100 到 eax② 读 counter100 到 eax旧值③ add eax,1 → 101④ 写回counter101⑤ add eax,1 → 101用的是旧值100⑥ 写回counter101覆盖了 B 的结果4.3 解法一std::mutex#includemutexintcounter0;std::mutex mtx;// 锁// 每个线程的循环里{std::lock_guardstd::mutexguard(mtx);// lockcounter;// 安全操作}// 自动 unlock锁的原理当某一线程持有该锁时其他线程得不到锁无法进入临界区只能等待持有锁的线程释放锁才能继续竞争锁并进入临界区。加了锁之后结果[mutex] 结果400000 (期望400000)代价是慢——每次加锁解锁涉及操作系统调度而其他线程只能阻塞等待。实际开发中不要锁整个大循环锁只需要保护的那一两行就好。4.4 解法二std::atomic硬件指令如果只是对单个整数做加减有更轻量的方案#includeatomicstd::atomicintcounter(0);// 循环里counter.fetch_add(1);fetch_add(1)在底层是一条 CPU 指令lock xadd [counter], 1 ; 读加写一条指令不可打断lock前缀会通知 CPU这条指令执行期间禁止其他核心访问这个内存地址。结果同样正确[atomic] 结果400000 (期望400000)而且比 mutex 快很多——没有系统调用带来的用户态/内核态切换开销没有阻塞等待纯用户态的一条硬件指令即可完成。4.5 三版对比版本结果说明不加锁255517竞态每次跑出来不一样mutex400000正确但慢atomic400000正确比 mutex 快atomic只适合保护单个变量。如果需要保护多步操作还是得用mutex。五、总结线程生命周期创建std::thread t(func)或std::thread t([]{...})构造后立即开始执行等待t.join()主线程阻塞直到子线程执行完毕若不调用则析构时触发 terminate分离t.detach()线程与 thread 对象脱离关联转为后台独立运行需保证其访问的变量生命周期覆盖线程执行期间引用传参std::thread(t, std::ref(x))thread 默认按值复制参数传递引用须用 std::ref 包装竞态解决方案场景方案原理说明单变量原子操作atomicT通过 CPU 原子指令如 x86 lock xadd实现无系统调用多变量或复合逻辑mutexlock_guard互斥锁保证临界区互斥访问lock_guard 以 RAII 方式管理锁生命周期异常安全保护lock_guard构造时加锁、析构时解锁作用域退出时自动释放count并非原子操作其底层对应三条汇编指令读→修改→写回线程调度可能发生在任意两条指令之间导致更新丢失。编译g-stdc17-pthreadmain.cpp-otest看到这里疑惑的几个问题应该都有答案了为什么需要多线程、怎么创建和等待线程、count 为什么会丢数据、mutex 和 atomic 分别怎么用。由于多线程并发编程内容繁多本文只是讲解最基础的部分后续会继续推进自旋锁、死锁、条件变量等内容逐步完善推进多线程系列。本人能力有限文章如有错误或遗漏的地方欢迎指正。

相关新闻

从Seq2Seq到Transformer:注意力机制原理与PyTorch实现详解

从Seq2Seq到Transformer:注意力机制原理与PyTorch实现详解

1. 从“注意力”到“注意力机制”:一个概念的进化史如果你在深度学习圈子里待过一阵子,肯定无数次听过“Attention”这个词。它就像是一个无处不在的“明星”,从机器翻译到图像识别,从语音合成到推荐系统,几乎每个前沿…

2026/7/23 4:17:06 阅读更多 →
10分钟训练高质量AI音色模型:RVC变声器完全指南

10分钟训练高质量AI音色模型:RVC变声器完全指南

10分钟训练高质量AI音色模型&#xff1a;RVC变声器完全指南 【免费下载链接】Retrieval-based-Voice-Conversion-WebUI Easily train a good VC model with voice data < 10 mins! 项目地址: https://gitcode.com/GitHub_Trending/re/Retrieval-based-Voice-Conversion-We…

2026/7/23 5:31:32 阅读更多 →
Cortex-M4F异常处理机制解析与故障诊断实战指南

Cortex-M4F异常处理机制解析与故障诊断实战指南

1. 从一次“死机”说起&#xff1a;为什么我们需要深入理解异常处理&#xff1f;如果你在嵌入式开发中遇到过系统突然“卡死”、程序跑飞或者某个中断服务程序&#xff08;ISR&#xff09;执行后整个应用逻辑就乱掉的情况&#xff0c;那么你很可能已经和Cortex-M处理器的异常处…

2026/7/23 2:34:20 阅读更多 →

最新新闻

PSO-XGBoost组合模型在工业预测中的优化与应用

PSO-XGBoost组合模型在工业预测中的优化与应用

1. 项目概述&#xff1a;当粒子群优化遇上XGBoost在工业预测和数据分析领域&#xff0c;我们常常需要处理复杂的非线性关系。传统单一算法往往难以兼顾预测精度和可解释性&#xff0c;这正是PSO-XGBoost组合模型大显身手的地方。这个项目完整实现了从参数优化到结果解释的全流程…

2026/7/23 18:55:43 阅读更多 →
西平装修亲测:2026年7月选这家省心

西平装修亲测:2026年7月选这家省心

2026年&#xff0c;西平全屋定制市场仍面临三大核心挑战&#xff1a;效率低下、成本失控、效果难控。据行业数据显示&#xff0c;73%的业主在装修过程中需要对接至少4个不同服务方&#xff08;设计师、施工队、建材商、家具商&#xff09;&#xff0c;平均耗时超6个月&#xff…

2026/7/23 18:55:43 阅读更多 →
【JAVA毕设源码分享】基于springboot大学生就业招聘系统的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于springboot大学生就业招聘系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

2026/7/23 18:55:43 阅读更多 →
Python毕设选题推荐:基于 Python 的金融教学期货模拟交易与数据分析系统 仿真期货交易演练系统的设计与实现【附源码、mysql、文档、调试+代码讲解+全bao等】

Python毕设选题推荐:基于 Python 的金融教学期货模拟交易与数据分析系统 仿真期货交易演练系统的设计与实现【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

2026/7/23 18:55:43 阅读更多 →
Formality:工具生成的文件

Formality:工具生成的文件

相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 Formality在启动和运行时会创建一些目录和文件&#xff0c;本文旨在对它们进行说明。 默认行为 在使用fm_shell命令或formality命令打开Formality后&#xff0c;首…

2026/7/23 18:54:43 阅读更多 →
计算机Python毕设实战-基于 Python 的金融期货仿真交易系统开发 大学生期货实训模拟交易管理系统设计【完整源码+LW+部署说明+演示视频,全bao一条龙等】

计算机Python毕设实战-基于 Python 的金融期货仿真交易系统开发 大学生期货实训模拟交易管理系统设计【完整源码+LW+部署说明+演示视频,全bao一条龙等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

2026/7/23 18:54:43 阅读更多 →

日新闻

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;从单点好评到指数级传播&#xff1a;AI副业主理人必须掌握的4层口碑渗透模型&#xff08;含ROI测算表&#xff09; 当AI副业主理人不再仅满足于单次服务交付&#xff0c;而是主动构建可复用、可裂变、可…

2026/7/23 0:00:25 阅读更多 →
AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析

AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析

更多请点击&#xff1a; https://codechina.net 第一章&#xff1a;AI写作开头钩子设计&#xff1a;为什么你的AI文案完读率不足18%&#xff1f;——基于2,346篇A/B测试报告的归因分析 在对2,346篇跨行业AI生成文案的A/B测试数据进行聚类分析后&#xff0c;我们发现&#xff1…

2026/7/23 0:01:26 阅读更多 →
Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南&#xff1a;免费开源的终极点对点安全聊天工具 【免费下载链接】chitchatter Secure peer-to-peer chat that is serverless, decentralized, and ephemeral 项目地址: https://gitcode.com/gh_mirrors/ch/chitchatter Chitchatter是一款革命性的安…

2026/7/23 0:01:26 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中&#xff0c;我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源&#xff0c;还是配置文件、证书等&#xff0c;都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下&#xff0c;但这…

2026/7/22 8:58:19 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP&#xff08;轻量级目录访问协议&#xff09;作为企业级身份认证的黄金标准&#xff0c;已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时&#xff0c;发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/22 19:43:43 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”&#xff0c;而是以可解释、可审计、可迭代的方式&#xff0c;赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/23 17:49:47 阅读更多 →

月新闻