《C++11:通过thread类编写C++多线程程序》
关于多线程的概念与理解,可以先了解Linux下的底层线程。当对底层线程有了一定程度理解以后,再学习语言级别的多线程编程就轻而易举了。
【Linux】多线程 -> 从线程概念到线程控制
【Linux】多线程 -> 线程互斥与死锁
语言级别的多线程编程最大的好处就是可以跨平台,使用语言级别编写的多线程程序不仅可以在Windows下直接编译运行,在Linux下也是可以直接编译运行的。其实本质还是调用了不同操作系统的底层线程API。
多线程简单使用
- 怎么创建启动一个线程?
#include<iostream>
#include<thread>
#include<chrono>void threadHandle1(int time)
{// 让线程休眠两秒std::this_thread::sleep_for(std::chrono::seconds(time));std::cout << "hello thread1" << std::endl;
}int main()
{// 创建一个线程对象,传入线程函数以及线程函数所需要的参数,线程就开始执行了std::thread t1(threadHandle1, 2);// 主线程等待子线程结束,再继续往下运行t1.join();std::cout << "main thread done!" << std::endl;return 0;
}

主线程要t1.join()等待子线程结束之后才能向后运行。如果不等待,就会异常终止。

- 子线程如何结束?
子线程函数运行完成就结束了。
也可以将线程设置为分离状态,主线程就可以不用等待子进程执行完再继续向后运行了。
void threadHandle1(int time)
{// 让线程休眠两秒std::this_thread::sleep_for(std::chrono::seconds(time));std::cout << "hello thread1" << std::endl;
}int main()
{// 创建一个线程对象,传入线程函数以及线程函数所需要的参数,线程就开始执行了std::thread t1(threadHandle1, 2);// 主线程等待子线程结束,再继续往下运行// t1.join();// 设置线程为分离状态t1.detach();std::cout << "main thread done!" << std::endl;return 0;
}

- 主线程如何处理子线程?
1、t1.join()等待线程,等待线程结束后主线程再继续向后运行。
2、t1.detach()分离线程,主线程结束,整个进程就结束了,所有线程都自动结束了。
#include<iostream>
#include<thread>
#include<chrono>void threadHandle1(int time)
{// 让线程休眠time秒std::this_thread::sleep_for(std::chrono::seconds(time));std::cout << "hello thread1" << std::endl;
}void threadHandle2(int time)
{// 让线程休眠time秒std::this_thread::sleep_for(std::chrono::seconds(time));std::cout << "hello thread1" << std::endl;
}int main()
{// 创建一个线程对象,传入线程函数以及线程函数所需要的参数,线程就开始执行了std::thread t1(threadHandle1, 2);std::thread t2(threadHandle2, 2);// 主线程等待子线程结束,再继续往下运行t1.join();t2.join();// 设置线程为分离状态// t1.detach();std::cout << "main thread done!" << std::endl;return 0;
}

mutex互斥锁和lock_guard
#include<iostream>
#include<thread>
#include<list>int tickets = 1000;void getTicket(int i)
{// 模拟用户抢票的行为while (tickets > 0){std::cout << "用户:" << i << "正在进行抢票!" << tickets << std::endl;tickets--;std::this_thread::sleep_for(std::chrono::microseconds(100));}
}int main()
{std::list<std::thread> list;for (int i = 1; i <= 3; i++){list.push_back(std::thread(getTicket, i));}for (std::thread &t: list){t.join();}return 0;
}

输出的结果杂乱无章,并且抢票出现负数。
多线程对临界区的访问会存在竞态条件:临界区代码段在多线程环境下执行,随着线程的调度时许不同,从而产生不同的结果。所以,我们要保证对临界区的原子操作,是通过对临界区加锁完成的。
由于多个线程同时向标准输出流std::cout输出信息,会造成输出混乱,各线程的输出可能相互穿插。
在多线程环境下,多个线程同时访问和修改共享资源tickets,会引发数据竞争问题。比如,当一个线程检查到tickets>0后,在执行tickets--操作之前,另一个线程也可能检查到tickets>0,进而导致重复售票或者出现负数票数的情况。
++/--操作并不是原子性的,其实是对应三条汇编指令完成的。
- 读取:从内存中把变量的值读取到寄存器
- 修改:在寄存器里将变量的值+1/-1
- 写入:把修改后的值写入到内存
在单线程环境下,这三个步骤顺序执行不会有问题。但是在多线程环境下,多个线程可能对同一个变量同时进行++/--操作,从而导致数据竞争的问题。
可以看下面的过程演示。
一:

二:

三:

C++11是通过加锁来保证++/--操作的原子性的。
#include<iostream>
#include<thread>
#include<list>
#include<mutex>int tickets = 1000;
std::mutex mtx; // 全局的一把锁void getTicket(int i)
{// 模拟用户抢票的行为while (tickets > 0){mtx.lock();// 锁+双重判断,如果不双重判断,当tickets为1时,用户正在抢票还没有执行到tickets--,其他用户判断tickets>0,也进来了等待锁。// 用户抢票之后,其他用户获取到锁还会进行抢票。if (tickets > 0){// 临界区代码段 - 加锁保护std::cout << "用户:" << i << "正在进行抢票!" << tickets << std::endl;tickets--;mtx.unlock();}}
}int main()
{std::list<std::thread> list;for (int i = 1; i <= 3; i++){list.push_back(std::thread(getTicket, i));}for (std::thread &t: list){t.join();}return 0;
}

通过加锁和双重判断的方式,这样就能做到对多线程对共享资源tickets的安全访问了。
lock_guard和unique_lock
其实不需要我们手动加锁和解锁,因为,如果临界区内有return,break等语句,此线程获取锁,但是在释放锁之前break或者renturn了,导致锁没有释放。那么其他线程也获取不了锁,就会造成死锁问题,所以对于这把互斥锁要有RAII的思想。
使用lock_guard。构造函数获取锁,析构函数释放锁。

int tickets = 1000;
std::mutex mtx; // 全局的一把锁void getTicket(int i)
{// 模拟用户抢票的行为while (tickets > 0){{std::lock_guard<std::mutex> lock(mtx);// mtx.lock();// 锁+双重判断,如果不双重判断,当tickets为1时,用户正在抢票还没有执行到tickets--,其他用户判断tickets>0,也进来了等待锁。// 用户抢票之后,其他用户获取到锁还会进行抢票。if (tickets > 0){std::cout << "用户:" << i << "正在进行抢票!" << tickets << std::endl;tickets--;}// mtx.unlock();}std::this_thread::sleep_for(std::chrono::microseconds(100));}
}
不管临界区是正常执行,还是break或return了,出了作用域,lock_guard会自动调用析构函数释放锁,保证所有线程都能释放锁,避免死锁问题发生。
lock_guard不支持拷贝构造和赋值运算符重载,如果需要拷贝和赋值可以使用unique_lock,支持移动语义,可以使用右值引用的拷贝构造和赋值运算符重载。


int tickets = 1000;
std::mutex mtx; // 全局的一把锁void getTicket(int i)
{// 模拟用户抢票的行为while (tickets > 0){{std::unique_lock<std::mutex> lck(mtx);// std::lock_guard<std::mutex> lock(mtx);// mtx.lock();// 锁+双重判断,如果不双重判断,当tickets为1时,用户正在抢票还没有执行到tickets--,其他用户判断tickets>0,也进来了等待锁。// 用户抢票之后,其他用户获取到锁还会进行抢票。if (tickets > 0){std::cout << "用户:" << i << "正在进行抢票!" << tickets << std::endl;tickets--;}// mtx.unlock();}std::this_thread::sleep_for(std::chrono::microseconds(100));}
}
总结:lock_guard不能用在函数传递或返回的过程中,因为lock_guard删除了拷贝构造和赋值,只能用在简单的临界区代码段的互斥操作中。unique_guard可以用在函数传递或返回的过程中,因为支持移动语义,可以使用移动构造和移动赋值。unique_guard通常与条件变量一起使用。
线程间的同步通信
生产者-消费者模型
std::mutex mtx;
std::condition_variable cv;// 边生产边消费
class Queue
{
public:void put(int val){// 加锁std::unique_lock<std::mutex> lock(mtx);// 如果队列不为空,则等待while (!que.empty()){// 1.进入等待状态 2.释放锁cv.wait(lock);// 被唤醒之后,等待状态进入阻塞状态,获取锁进入就绪状态继续执行}que.push(val);cv.notify_all(); // 通知消费者消费std::cout << "生产者 生产:" << val << "号物品" << std::endl;}int get(){// 加锁std::unique_lock<std::mutex> lock(mtx);// 如果队列为空,则等待while (que.empty()){// 1.进入等待状态 2.释放锁cv.wait(lock);// 被唤醒之后,等待状态进入阻塞状态,获取锁进入就绪状态继续执行}int val = que.front();que.pop();cv.notify_all(); // 通知生产者生产std::cout << "消费者 消费:" << val << "号物品" << std::endl;return val;}
private:std::queue<int> que;
};// 生产
void producer(Queue* que)
{for (int i = 0; i <= 10; i++){que->put(i);std::this_thread::sleep_for(std::chrono::microseconds(100));}
}
// 消费
void conducer(Queue* que)
{for (int i = 0; i <= 10; i++){que->get();std::this_thread::sleep_for(std::chrono::microseconds(100));}
}
int main()
{Queue que;std::thread p(producer, &que);std::thread c(conducer, &que);p.join();c.join();return 0;
}

相关文章:
《C++11:通过thread类编写C++多线程程序》
关于多线程的概念与理解,可以先了解Linux下的底层线程。当对底层线程有了一定程度理解以后,再学习语言级别的多线程编程就轻而易举了。 【Linux】多线程 -> 从线程概念到线程控制 【Linux】多线程 -> 线程互斥与死锁 语言级别的…...
19-dfs-排列数字(基础)
题目 来源 842. 排列数字 - AcWing题库 思路 由于相对简单,是dfs的模板题,具体思路详见代码 代码 #include<bits/stdc.h> using namespace std; const int N10; int state[N],path[N];//是否使用过,当前位置 int n; void dfs(int …...
32.代码题
接着上集...... 派对:超时了,总该受到惩罚吧? 洛西:至于吗?就0.1秒! 晴/宇:十分应该。 洛西:我..................... 没办法,洛西只能按照要求去抓R了。 1.P1102 …...
nacos 3.x Java SDK 使用详解
Nacos 3.x Java SDK 使用详解 Nacos 3.x 是云原生服务治理的重要升级版本,其 Java SDK 在性能、协议和扩展性上均有显著优化。 一、环境要求与依赖配置 基础环境 JDK 版本:需使用 JDK 17(Nacos 3.x 已放弃对 JDK 8 的支持)。Spri…...
SPI-NRF24L01
模块介绍 NRF24L01是NORDIC公司生产的一款无线通信芯片,采用FSK调制,内部集成NORDIC自己的Enhanced Short Burst 协议,可以实现点对点或者1对6 的无线通信,通信速率最高可以达到2Mbps. NRF24L01采用SPI通信。 ①MOSI 主器件数据输出…...
python黑科技:无痛修改第三方库源码
需求不符合 很多时候,我们下载的 第三方库 是不会有需求不满足的情况,但也有极少的情况,第三方库 没有兼顾到需求,导致开发者无法实现相关功能。 如何通过一些操作将 第三方库 源码进行修改,是我们将要遇到的一个难点…...
一区严选!挑战5天一篇脂质体组学 DAY1-5
Day 1! 前期已经成功挑战了很多期NHANES啦!打算来试试孟德尔随机化领域~ 随着孟德尔随机化研究的普及,现在孟德尔发文的难度越来越高,简单的双样本想被接收更是难上加难,那么如何破除这个困境,这次我打算…...
【JavaScript】合体期功法——DOM(二)
目录 DOM事件监听案例关闭广告随机点名 事件监听版本事件类型 DOM 事件监听 事件:编程时系统内发生的动作或事情,例如用户在网页上单击一个按钮 事件监听:让程序检测是否产生事件,一旦事件触发,立即调用函数做出响应…...
23种设计模式中的中介者模式
定义了一个中介对象来封装一系列对象之间的交互。中介者使各对象直接不再显示地相互引用,从而使其松散耦合,且可以独立地改变它们之间的交互。 通过引入一个中介者对象,来协调和封装多个对象之间的交互,从而降低他们之间的耦合度。…...
量子计算:开启未来计算的新纪元
一、引言 在当今数字化时代,计算技术的飞速发展深刻地改变了我们的生活和工作方式。从传统的电子计算机到如今的高性能超级计算机,人类在计算能力上取得了巨大的进步。然而,随着科技的不断推进,我们面临着越来越多的复杂问题&…...
Docker 的实质作用是什么
Docker 的实质作用是什么 目录 Docker 的实质作用是什么**1. Docker 的实质作用****2. 为什么使用 Docker?****(1)解决环境一致性问题****(2)提升资源利用率****(3)简化部署与扩展****(4)加速开发与协作****3. 举例说明****总结**Docker 的实质是容器化平台,核心作用…...
Assembly语言的装饰器
Assembly语言的装饰器:灵活高效的代码复用 引言 在软件开发中,代码复用和模块化是两个至关重要的概念。它们不仅使得代码的维护变得更为简单,而且能极大提升开发效率。在高级语言中,装饰器是一种非常受欢迎的设计模式࿰…...
VITA 模型解读,实时交互式多模态大模型的 pioneering 之作
写在前面:实时交互llm 今天回顾一下多模态模型VITA,当时的背景是OpenAI 的 GPT-4o 惊艳亮相,然而,当我们将目光投向开源社区时,却发现能与之匹敌的模型寥寥无几。当时开源多模态大模型(MLLM),大多在以下一个或多个方面存在局限: 模态支持不全:大多聚焦于文本和图像,…...
自学-408-《计算机网络》(总结速览)
文章目录 第一章 计算机网络概述1. 计算机网络的定义2. 计算机网络的基本功能3. 计算机网络的分类4. 计算机网络的层次结构5. 计算机网络的协议6. 计算机网络的组成部分7. 计算机网络的应用8. 互联网的概念 物理层的主要功能第二章 数据链路层和局域网1. 数据链路层的功能2. 局…...
AF3 FeaturePipeline类解读
AlphaFold3 feature_pipeline 模块 FeaturePipeline 类是一个封装类,通过调用函数np_example_to_features 实现整个数据处理流程。 源代码: def np_to_tensor_dict(np_example: Mapping[str, np.ndarray],features: Sequence[str], ) -> TensorDict:"""C…...
【质量管理】纠正、纠正措施和预防的区别与解决问题的四重境界
“质量的定义就是符合要求”,我们在文章【质量管理】人们对于质量的五个错误观念-CSDN博客中提到过,这也是质量大师克劳士比所说的。“质量的系统就是预防”,防止出现产品不良而造成的质量损失。 质量问题的解决可以从微观和宏观两个方面来考…...
Java面试黄金宝典24
1. 什么是跳表 定义 跳表(Skip List)是一种随机化的数据结构,它基于有序链表发展而来,通过在每个节点中维护多个指向其他节点的指针,以多层链表的形式组织数据。其核心思想是在链表基础上增加额外层次,每…...
Windows 11系统下Kafka的详细安装与启动指南(JDK 1.8)
1. 安装前准备 在Windows 11系统中安装Kafka之前,需要确保满足以下条件: 1.1 系统要求 Windows 11操作系统(64位)至少4GB内存(建议8GB或更高)至少5GB可用磁盘空间管理员权限1.2 所需工具 浏览器(用于下载软件)解压工具(如7-Zip、WinRAR,Windows 11自带的解压功能也…...
树莓派超全系列文档--(16)无需交互使用raspi-config工具其三
无需交互使用raspi-config工具其三 无需交互的 raspi-configAdvanced optionsExpand filesystemNetwork interface namesNetwork proxy settingsBoot orderBootloader versionWaylandAudio config Update 文章来源: http://raspberry.dns8844.cn/documentation 原文…...
【蓝桥杯】算法笔记1
1.暴力枚举 给定一个正整数n,请找出所有满足a + b = n的整数对(a, b),其中a和b都是正整数,且a ≤ b。 输入格式:一个正整数n (1 ≤ n ≤ 10⁶) 输出格式:所有符合条件的(a, b)对,每行一对,按a的升序排列。如果没有符合条件的对,输出"No solution"。 问题分…...
爱因斯坦求和 torch
目录 向量点积 矩阵乘法 矩阵转置 向量转换相机坐标系 在 Python 的科学计算库(如 NumPy)中,einsum 是一个强大的函数,它可以简洁地表示各种张量运算。下面是几个不同类型的使用示例: 向量点积 向量点积是两个向量…...
Linux命令-sed指令
sed命令参数: 基本参数 -n:抑制默认输出,只显示匹配的行。 -e:指定 sed 脚本。 -i:直接修改文件内容。 -f:指定包含 sed 脚本的文件。 -r:启用扩展正则表达式。 常用操作 s:替换字符…...
新手SEO优化实战快速入门
内容概要 对于SEO新手而言,系统化掌握基础逻辑与实操路径是快速入门的关键。本指南以站内优化为切入点,从网站结构、URL设计到内链布局,逐层拆解搜索引擎友好的技术框架;同时聚焦关键词挖掘与内容策略,结合工具使用与…...
如何使不同的窗体控件,适应不同分辨率的屏幕?
问题 当屏幕分辨率提高或降低时,原分辨率显示正常的控件,将变得很小或很大,字体也变得太大或太小。 解决办法 当分辨率变化时,采用递归的方法,对所有的控件放大或缩小。 public static void MainForm_Load(object s…...
sqli-labs靶场 less 11
文章目录 sqli-labs靶场less 11 POS联合注入 sqli-labs靶场 每道题都从以下模板讲解,并且每个步骤都有图片,清晰明了,便于复盘。 sql注入的基本步骤 注入点注入类型 字符型:判断闭合方式 (‘、"、’、“”&…...
tomcat部署项目打开是404?
问题描述 今天在帮助一个小伙伴解决问题的时候 部署成功了 就是打不开总是404 他这个项目是公司的一个18年的项目 巨老!!! HTTP状态 404 - 未找到 类型 状态报告 描述 源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的资源表示…...
[Linux]解决虚拟机 ubantu系统下网络的问题
问题来源:打开ubantu发现网络连接标识消失 解决步骤: 重新安装,前面操作无效 切换桥接模式、直连手机网络 已解决:...
如何使用stable diffusion 3获得最佳效果
参考:How to get the best results from Stable Diffusion 3 Scaling Rectified Flow Transformers for High-Resolution Image Synthesis prompting SD3 不再受限于CLIP的最长77个token的长度限制,可以输入更长的prompt。 (两个CLIP模型的…...
SakuraCat(2)Endpoint
Endpoint 功能概述 监听指定端口(默认是 8080)的客户端连接。接受客户端连接后,为每个连接创建一个新的线程进行处理。使用 Processor 类来处理客户端的请求和响应。 package com.SakuraCat.connector.protocolHandler;import com.SakuraC…...
Java学习笔记1——编程基础
一、整数类型变量 注意:每个字符型常量占两个字节 二、自动类型转换和强制类型转换 三、算术运算符 四、赋值运算符 五、比较运算符 六、逻辑运算符 七、运算符的优先级 运算符的优先级可以通过以下口诀来记忆: 括号优先,单目次之&am…...
