C++多线程并发
文章目录
- C++多线程并发
- std::chrono
- C++中的多线程:std::thread
- 主线程等待子线程结束:join
- 主线程分离子线程:detach
- 异步:std::async
- 异步的另一种用法:std::launch::deferred
- std::async的底层实现:std::promise
- std::future的tip
- 互斥量
- std::mutex 上锁
- std::lock_guard
- std::unique_lock
- try_lock
- 死锁
- 读写锁:shared_mutex
- 条件变量(信号量)
- 等待某个条件成真
- 案例
- 原子操作
C++多线程并发
std::chrono
C++11开始引入时间标准库
- 利用C++强类型的特点,明确区分时间点和时间段,明确区分不同的时间单位。
// 时间点的例子:2022年1月8日 13点07分10秒
// 时间段的例子:1分30秒
// 时间点类型:chrono::steady_clock::time_point
// 时间段类型:chrono::milliseconds, chrono::seconds, chrono::munutes等
// 方便的元素安抚重载:时间点+时间段=时间点,时间点-时间点=时间段
auto t0 = chrono::steady_clock::now();
auto t1 = t0 + chrono::seconds(30);
auto dt = t1 - t0;
int64_t sec = chrono::duration_cast<chrnon::seconds>(dt).count()
- 跨平台的sleep:std::this_thread::sleep_for
可以使用std::this_thread::sleep_for替换unix的usleep。他可以让当前线程休眠一段时间,然后继续
而且单位也可以自己指定,比如milliseconds表示毫秒,也可以换成是microseconds表示微妙,seconds表示秒,chrono的强类型让单位选择更自由
std::this_thread::sleep_for(chrono::milliseconds(1000));
- 为什么需要多线程:无阻塞多任务
我们的程序常常需要同时处理多个任务:例如后台执行一个很耗时的任务,比如下载一个文件,同时还要和用户交互。在GUI应用程序中很常见,比如浏览器在后台下载文件的同时,用户仍然可以用鼠标操作其GUI界面
C++中的多线程:std::thread
- C++11开始,为多线程提供了语言级别的支持。他用std::thread这个类来表示线程
- std::thread构造函数的参数可以是任意的lambda表达式
- 当那个线程启动的时候,就会执行这个lambda表达式里面的内容
- 这样就可以一边和用户交互,一边在另外一个线程里面慢吞吞的下载文件了
在cmake中为了跨平台引入线程包,cmake引入了自己的线程包
find_package(Threads REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC Threads::Threads)
主线程等待子线程结束:join
如果我们遇到子线程还没有执行完成的时候,主线程就退出的情况,我们则不要着急推出主线程,要让主线程等待子线程结束再退出。我们可以使用std::thread()类的成员函数join()来等待进程的结束。
主线程分离子线程:detach
std::thread的析构函数会销毁线程。作为一个C++类,std::thread同样遵循着RAII思想和三五法则:因为管理着资源,他自定义了析构函数,删除了拷贝构造/赋值构造,但是提供了移动构造/赋值函数
thread(thread&& _Other) noexcept : _Thr(_STD exchange(_Other._Thr, {})) {}thread& operator=(thread&& _Other) noexcept {if (joinable()) {_STD terminate();}_Thr = _STD exchange(_Other._Thr, {});return *this;}thread(const thread&) = delete;thread& operator=(const thread&) = delete;void swap(thread& _Other) noexcept {_STD swap(_Thr, _Other._Thr);}
因此当thread所在的函数退出的时候,就会调用thread的析构函数,这会销毁该线程这个时候我们调thread::detach()函数,分离该线程–意味着线程的生命周期不再由std::thread对象管理,而是在线程退出以后自动销毁自己。不过进程退出的时候,线程还是会自动退出。
异步:std::async
- std::async是一个接受带有返回值lambda自身返回一个std::future对象
- lambda的函数体可以在另一个线程里面执行
- 接下来你可以在main里面做一些彼得事情,你可以在线程函数内执行别的操作
- 最后调用future的get方法,如果此时线程函数未执行完成,会等待执行完成,并且获取对应的返回值
#include <iostream>
#include <chrono>
#include <thread>
#include <future>
using namespace std;
int test_function(int a)
{std::this_thread::sleep_for(chrono::seconds(a));return a;
}
void test()
{std::cout << "test" << std::endl;
}
int main() {std::future<int> fret = std::async([&](){return test_function(10);});test();// fret.wait();int ret = fret.get();std::cout << "ret:" << ret << std::endl;return 0;
}
当然除了get会等待线程执行完毕之外,wait也可以等待线程执行完成,但是不会返回其值
只要线程没有执行完成,wait会一直等下去,而使用wait_for则可以指定一个最长的等待时间,用chrono里的类表示单位。他会返回一个std::future_status表示等待是否成功。如果超过这个等待时间线程还没有执行完毕,则放弃等待,返回future_status::timeout。
如果线程在指定时间内执行完毕,则认为等待成功,返回future_status::ready。同理还有wait_until其参数是一个时间点。
异步的另一种用法:std::launch::deferred
std::async的第一个参数可以设置成std::launch::deferred,这个时候不会创建一个线程来执行,他只会把lambda函数体内的运算推迟到future的get被调用时。这种写法,执行函数仍然在主线程中执行,他只是函数式变成范式意义上的异步,而不涉及到真正的多线程。可以用这个实现惰性求值之类的需求。
std::async的底层实现:std::promise
如果不想让std::async帮你自动创建线程,想要手动创建线程,可以使用std::promise。然后再线程返回的时候,
用set_value设置返回值。再主线程中是哟个get_future获取future对象,进一步get可以等待并且获取线程的返回值。
#include <iostream>
#include <chrono>
#include <thread>
#include <future>using namespace std;int test_function(int a)
{std::this_thread::sleep_for(chrono::seconds(a));return a;
}void test()
{std::cout << "test" << std::endl;
}int main() {std::promise<int> pret;std::thread t1([&](){auto ret = test_function(5);pret.set_value(ret);});std::future<int> fret = pret.get_future();test();int ret = fret.get();std::cout << "ret:" << ret << std::endl;t1.join();return 0;
}
std::future的tip
future为了三五法则,阐述了拷贝构造/赋值函数。如果需要浅拷贝,实现共享同一个future对象,可以使用std::shared_future。
如果不需要返回值,std::async里面的lambda返回类型可以设置魏void,future对象的类型也设置成std::future<void>。
同理有std::promise<void>,他的set_value()不接受参数,仅仅作为同步用,不传递任何实际的值
互斥量
多线程打架问题:两个线程同时往一个vector中推数据,程序崩溃了,这是一位vector不是一个线程安全的容器,多个线程同时访问同一个vector会出现数据竞争的现象。
#include <iostream>
#include <chrono>
#include <thread>
#include <future>using namespace std;vector<int> aa;int main() {std::thread t1([&](){while(true){aa.push_back(1);std::this_thread::sleep_for(std::chrono::microseconds(0));}});std::thread t2([&](){while(true){aa.push_back(2);std::this_thread::sleep_for(std::chrono::microseconds(0));}});t1.join();t2.join();return 0;
}
std::mutex 上锁
调用std::mutex的lock()时,会检测mutex是否已经上锁,如果没有锁定,则上锁,如果已经锁定,则陷入等待,直到mutex被另一个线程解锁后才再次上锁。而调用unlock则会进行解锁操作。这样就可以保证mtx.lock和mtx.unlock之间的代码段,同一时间只有一个线程在执行,从而避免数据竞争。
std::lock_guard
std::lock_guard是一个在构造的时候调用mtx.lock(),在析构的时候自动调用mtx.unlock()。从而退出函数作用域的时候能够自动解锁,避免程序员不小心忘记解锁
std::unique_lock
std::lock_guard严格在析构的时候unlock(),但是有的时候我们会希望提前unlock()。这个时候就可以std::unique_lock,它额外存储了一个flag表示是否已经释放。他会在结构的时候检测这个flag,如果没有释放,则调用unlock(),否则不调用。
然后可以直接调用unique_lock的unlock(),函数来提前解锁,但是即使忘记解锁也没有关系,退出作用域的时候他还会自动检查一遍是否需要解锁
std::unique_lock的构造函数还可以有一个额外的参数,那就是std::defer_lock
指定了这个参数的话,std::unique_lock不会在构造函数中调用mtx.lock(),需要在之后手动调用lock()函数才能上锁,好处依然是忘记unlock(),也能自动调用unlock()。
try_lock
lock()函数会等待到mutex对象到解锁状态,我们也可以使用try_lock(),在上锁失败的时候不会等待,而是直接返回false,如果上锁成功则返回true
其中try_lock_for表示等待时间,需要使用std::time_mutex ,同理还是又try_lock_until()。
std::unique_lock可以使用std::try_to_lock做参数,和无参数相比,他会调用mtx.try_lock而不是mtx.lock()。之后可以使用grd.owns_lock()判断是否上锁成功。如果使用的参数是std::adopt_lock做参数,则表示mtx是已经上锁的。
死锁
解决办法:
-
最简单的是一个线程永远不要持有两个锁,分别上锁,同样也可以避免死锁
-
保证双方上锁顺序一致
-
如果没有办法保证上锁顺序一致,可以试用贴std::lock(mtx1, mtx2, …)函数,一次性地对多个mutex对象上锁。他接受任意多个mutex作为参数,并且他保证在无论任意线程中调用的顺序是否相同,都不会产生死锁问题。
-
与std::lock_guard相对应,std::lock也有std::scoped_lock。只不过他可以同时对多个mutex上锁。
std::scoped_lock(mtx1, mtx2, ...) -
如果是自己锁自己的话,可以使用std::recursive_mutex来保证一个线程lock多次lock同一个锁的时候不会产生死锁
读写锁:shared_mutex
条件变量(信号量)
std::condition_variable cv;
cv.wait(lock)将会让当前线程陷入等待
在其他线程中调用cv.notify_one()则会唤醒一个正在等待的线程。
可以发现std::condition_variable必须和一个std::unique_lock一起用。
int main()
{condition_variable ccv;mutex mtx;std::thread t1([&]{unique_lock lck(mtx);ccv.wait(lck);std::cout << "t1 is wait" << std::endl;});std::this_thread::sleep_for(std::chrono::milliseconds(400));std::cout << "notify......" << std::endl;ccv.notify_one();t1.join();return 0;
}
等待某个条件成真
还可以给wait指定一个额外的参数,变成wait(lck, expr) 的形式,其中expr是一个lambda表达式,只有返回值为true时才会真正的唤醒,否则继续等待。
int main()
{condition_variable ccv;mutex mtx;bool ready = false;std::thread t1([&]{unique_lock lck(mtx);ccv.wait(lck, [&](){return ready;});std::cout << "t1 is wait" << std::endl;});std::this_thread::sleep_for(std::chrono::milliseconds(400));std::cout << "notify1......" << std::endl;ccv.notify_one();ready = true;std::cout << "notify2......" << std::endl;ccv.notify_one();t1.join();return 0;
}
如果有多个等待者,可以使用notify_all()来唤醒所有的等待线程,这就是为啥wait需要一个unique_lock作为参数,因为要保证多个线程被唤醒时候,只有一个能被启动。如果不需要,在wait返回之后调用lck.unlock()即可。顺便一提,wait的过程中会暂时unlock这个锁
std::condition_variable只支持std::unique_lock作为wait参数,如果需要其他类型的mutex锁,可以使用std::condition_variable_any
还有wait_for和wait_until函数,分别接收chrono时间段和时间点作为参数
案例
生产者消费者模式
类似于消费队列
#include <string>
#include <deque>
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <vector>
using namespace std;
int main()
{condition_variable ccv;mutex mtx;vector<int> datas;std::thread t1([&]{for (int i = 0; i < 2; i++){unique_lock lck(mtx);ccv.wait(lck, [&](){return datas.size() != 0;});auto it = datas.back();datas.pop_back();lck.unlock();std::cout << "t1 is wait " << it << std::endl;}});std::thread t2([&]{for (int i = 0; i < 2; i++){unique_lock lck(mtx);ccv.wait(lck, [&](){return datas.size() != 0;});auto it = datas.back();datas.pop_back();lck.unlock();std::cout << "t2 is wait " << it << std::endl;}});datas.push_back(1);ccv.notify_one();datas.push_back(2);ccv.notify_one();datas.push_back(3);datas.push_back(4);ccv.notify_all();t1.join();t2.join();return 0;
}
原子操作
atomic 有专门的硬件指令加持
cpu识别到该指令的时候会锁住内存总线,放弃乱序执行等优化策略(将该指令视为一个同步点,强制同步掉之前所有的内存操作),从而向你保证该操作是原子的,不会执行到中途另外一个线程插一脚进来。
对于程序员,只需要把int修改成atomic<int> 即可,不需要像mutex那样需要手动上锁解锁,因此使用起来也比较直观
int main()
{condition_variable ccv;std::atomic<int> counter = 0;std::thread t1([&]{for (int i = 0; i < 10000; i++){// counter = counter+1;counter++;}});std::thread t2([&]{for (int i = 0; i < 10000; i++){// counter = counter+1;counter++;}});t1.join();t2.join();std::cout << counter << std::endl;return 0;
}
注意:注释起来的代码不能保证执行的原子性
除了使用运算符重载,还可以使用
// fetch_add: 和+=等价
// store: 和=等价
// load: 读取对应的值
// exchange(val)会把val写入原子变量,并返回old值
// compare_exchange_strong 读取,比较是否相等,不相等则写入
相关文章:
C++多线程并发
文章目录 C多线程并发std::chronoC中的多线程:std::thread主线程等待子线程结束:join主线程分离子线程:detach异步:std::async异步的另一种用法:std::launch::deferredstd::async的底层实现:std::promisest…...
新火种AI|摊上事儿了!13名OpenAI与谷歌员工联合发声:AI失控可能导致人类灭绝...
作者:小岩 编辑:彩云 2024年,OpenAI的CEO Sam Altman就没有清闲过,他似乎一直走在解决麻烦的路上。最近,他的麻烦又来了。 当地时间6月4日,13位来自OpenAI和Google Deep Mind的现任及前任员工联合发布了…...
Web前端后端精通:深度解析与技能进阶
Web前端后端精通:深度解析与技能进阶 在数字时代的浪潮中,Web前端后端技术的精通成为了信息科技领域的核心竞争力。本文将从四个方面、五个方面、六个方面和七个方面深入探讨Web前端后端技术的精髓,带领读者领略这一领域的魅力与挑战。 一、…...
【C语言】09.函数递归
递归其实是⼀种解决问题的方法,在C语言中,递归就是函数自己调用自己。 一、递归的介绍 1.1递归的思想 把⼀个大型复杂问题层层转化为⼀个与原问题相似,但规模较小的子问题来求解;直到子问题不能再被拆分,递归就结束…...
php高级之框架源码、宏扩展原理与开发
在使用框架的时候我们经常会看到如下代码 类的方法不会显示地声明在代码里面,而是通过扩展的形式后续加进去,这么做的好处是可以降低代码的耦合度、保证源码的完整性、团队开发的时候可以分别写自己的服务去扩展类,减少代码冲突等等。我自己…...
(2024,示例记忆,模型记忆,遗忘,差分评估,概率评估)深度学习中的记忆:综述
Memorization in deep learning: A survey 公和众和号:EDPJ(进 Q 交流群:922230617 或加 VX:CV_EDPJ 进 V 交流群) 目录 0 摘要 1 引言 0 摘要 深度神经网络(DNNs)驱动的深度学习ÿ…...
硬件产品经理
边端协调管理平台 主页一:模型管理1.1 边侧模型管理 二:配置管理2.1 终端软件配置管理 三:设备管理3.1 区域位置管理3.2 工控机管理(其实就是围绕授权)3.3 生产设备管理3.4 设备运行管理 四:数据服务4.1 实…...
AES加密、解密工具类
1、AES加密、解密工具类 这篇文章,主要记录一下AES加密、解密的工具类代码,在需要使用的时候,直接复制黏贴即可。 package com.gitcode.pms.common.util;import org.slf4j.Logger; import org.slf4j.LoggerFactory;import javax.crypto.Cipher; import javax.crypto.spec.…...
普通人想要自学ai,该如何入手,看完这篇你就懂了,零基础教程!
学会了AIGC之后,我只想说:无敌是多么寂寞? 之前我整理一篇会议记录起码要2小时。现在交给AI ,5分钟搞定; 之前整理账目总是出错,现在利用AI财务整合器,轻松解决统计难题; 之前写个…...
Less的简单总结
Less 是一个开源的 CSS 预处理器,它扩展了 CSS 语言,增加了变量、嵌套规则、运算符、函数等特性,使编写 CSS 更加高效、灵活且易于维护。下面是对 "Less" 的一个总结文档: 简介 名称:Less(通常表…...
Android:UI:Drawable:View/ImageView与Drawable
文章目录 在View/ImageVIew中显示DrawableDrawable对View的更新操作在View/ImageVIew中显示Drawable API View.setBackground(Drawable) ImageView.setImagDrawable(Drawable) 源码分析 View.mBackground在View.draw(Canvas)中绘制,调用Drawable.draw(Canvas) ImageView.m…...
网络安全实验BUAA-全套实验报告打包
下面是部分BUAA网络安全实验✅的实验内容 : 认识路由器、交换机。掌握路由器配置的基本指令。掌握正确配置路由器的方法,使网络正常工作。 本博客包括网络安全课程所有的实验报告:内容详细,一次下载打包 实验1-路由器配置实验2-AP…...
监控易监测对象及指标之:全面监控SQL Server 2008
随着企业信息化建设的不断深入,数据库作为存储和管理关键业务数据的核心,其稳定性和性能至关重要。SQL Server 2008作为一款广泛使用的关系型数据库管理系统,承载着众多企业的核心业务数据。 为了确保SQL Server 2008数据库的稳定运行和高效性…...
【学习记录】6.11 阅读记录
SpringBoot多环境配置详解(application-dev.yml、application-test.yml、application-prod.yml) springboot集成mybatis【使用generatorConfig.xml配置自动生成代码】 怎么快速查看自己mysql的安装位置 解决 http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd 报错...
100TOPS算力!16GB内存顶配NVIDIA Jetson Orin NX 16GB 开箱
观前提醒:你以为我斥资6600买了一个NX玩?我其实买了三个NX NVIDIA Jetson Orin NX 简介: NVIDIA Jetson Orin NX是NVIDIA推出的一款高性能边缘计算平台,其设计目标是提供卓越的计算能力以支持各种复杂的人工智能(AI&am…...
OCP学习笔记-007 SQL语言之一:DQL
1. DQL - Data Query Language 命令行提示符修改 SQL> set time on 10:33:58 SQL> define DEFINE _DATE = "11-DEC-22" (CHAR) DEFINE _CONNECT_IDENTIFIER = "orcl" (CHAR) DEFINE _USER = "SYS" (CHAR) DEFINE _P…...
Git之解决重复输入用户名和密码(三十九)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…...
Python 机器学习 基础 之 【实战案例】轮船人员获救预测实战
Python 机器学习 基础 之 【实战案例】轮船人员获救预测实战 目录 Python 机器学习 基础 之 【实战案例】轮船人员获救预测实战 一、简单介绍 二、轮船人员获救预测实战 三、数据处理 1、导入数据 2、对缺失数据的列进行填充 3、属性转换,把某些列的字符串值转换为数字…...
安全相关的一些基础知识(持续更新)
目录 1. TRNG真随机数生成 2. 对称加密和非对称加密及其区别 3. Hash算法(摘要算法) 4. HTTPS、TLS、SSL、HTTP区别和关系 HTTPS的基本原理 5. PSS 1. TRNG真随机数生成 True Random Number Generator 在真随机数的生成里,把随机数的生…...
使用TensorFlow和Keras对以ResNet50模型进行微调
以下是使用ResNet50进行微调以识别特定的新东西的代码演示。将使用TensorFlow和Keras进行这个任务。 数据集下载地址,解压到工程里面去: https://www.kaggle.com/datasets/marquis03/cats-and-dogs原始代码: from keras.applications…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
PHP 8.5 即将发布:管道操作符、强力调试
前不久,PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5!作为 PHP 语言的又一次重要迭代,PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是,借助强大的本地开发环境 ServBay&am…...
快速排序算法改进:随机快排-荷兰国旗划分详解
随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...
对象回调初步研究
_OBJECT_TYPE结构分析 在介绍什么是对象回调前,首先要熟悉下结构 以我们上篇线程回调介绍过的导出的PsProcessType 结构为例,用_OBJECT_TYPE这个结构来解析它,0x80处就是今天要介绍的回调链表,但是先不着急,先把目光…...
高端性能封装正在突破性能壁垒,其芯片集成技术助力人工智能革命。
2024 年,高端封装市场规模为 80 亿美元,预计到 2030 年将超过 280 亿美元,2024-2030 年复合年增长率为 23%。 细分到各个终端市场,最大的高端性能封装市场是“电信和基础设施”,2024 年该市场创造了超过 67% 的收入。…...
