【C++】C++11的新特性 — 线程库 ,原子操作 , 条件变量

在这里插入图片描述

勇敢就是接受发生在你身上的事,并把它尽力做到最好。
-- 约翰・欧文 --

C++11的新特性

  • 1 线程
    • 1.1 线程概念
    • 1.2 C++中的线程
    • 1.3 线程并行
    • 1.4 锁
  • 2 原子操作
  • 3 条件变量
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 线程

1.1 线程概念

在Linux中我们了解了什么是线程:
【Linux】从零开始认识多线程 — 线程概念与底层实现
【Linux】从零开始认识多线程 — 线程控制
【Linux】从零开始认识多线程 — 线程ID
【Linux】从零开始认识多线程 — 线程互斥

线程:在进程内部运行,是CPU调度的基本单位,共享一个地址空间。Linux下线程本质是一种轻量化进程,可以在一个进程中并发运行不同的任务。同时Linux为了避免出现同时访问的问题,保证线程互斥,可以加入互斥锁!

在语言层,每个语言都封装了线程库,内部封装了底层的系统调用,让上层更加方便的使用。

1.2 C++中的线程

c++中线程被设计成了一个类来方便我们的使用:
在这里插入图片描述

我们可以快捷通过创建一个对象来快速创建线程,也可以调用对象的join接口来进行等待!

我们来看构造函数:
在这里插入图片描述
默认构造是创建一个无参的空线程。一般创建时要传入需要执行的函数方法,和一个参数包!在linux下,如果我们想要传入多个参数,就要想办法将这些参数进行一个整合,即在堆上开辟一个结构体来让线程获取。而在C++11中,不需要进行结构体的传递,通过可变参数包的方法就可以满足!

来看一个例子:

void Print(int n)
{for (int i = 0; i < n; i++){cout << i << endl;}cout << endl;
}int main()
{thread t(Print , 100);t.join();return 0;
}

我们构造的时候加入print100,就可以单独设置一个线程来运行Print函数。
在这里插入图片描述
获取线程id接口 get_id

  1. 主线程中获取新线程的id直接进行调用即可
  2. Print接口中获取自己的id就比较复杂,因为Print函数中并没有线程对象?那怎么办可以通过this_thread类(全局的一些数据)。可以通过this_threadget_id来获取主线程id,来侧面验证

在这里插入图片描述

1.3 线程并行

再来运行两个线程我们来看看:
在这里插入图片描述
并行执行会出现一些换行的问题!
当对同一个全局变量进行操作时,如果操作不是原子的,就很有可能导致一些错误,这些错误是偶发性的,不容易复刻。我们来通过一个大数据情况下的运行看看:
在这里插入图片描述
为了避免这样运行的错误,我们可以进行加锁:

  1. mutex类是对底层的锁的系统调用进行的封装
  2. 通过lockunlock可以快捷的进行上锁解锁!
int x = 0;
//全局锁
mutex tex;void Print(int n)
{//这个for循环不是临界区,处在线程独立的栈中for (int i = 0; i < n; i++){tex.lock();//对全局数据的操作是临界的x++;tex.unlock();}
}
int main()
{thread t1(Print, 10000);thread t2(Print, 20000);t1.join();t2.join();cout << x << endl;return 0;
}

在进入临界区之前加锁,保证非原子的操作中不会受到其他线程的打扰!同样的也会发生频繁的上下文切换,导致运行效率变低!

如果不使用全局变量呢?我们可以通过参数传递过去:

void Print(int n , int& rx , mutex& rtex)
{for (int i = 0; i < n; i++){tex.lock();x++;tex.unlock();}
}int main()
{mutex tex;int x = 0;//这样无法直接传入过去thread t1(Print, 10000 , x , tex);thread t2(Print, 20000 , x , tex);t1.join();t2.join();cout << x << endl;return 0;
}

当我们向这样运行的时候,就会发现,报错了!
在这里插入图片描述
因为这里 x , tex不是直接传入到Print函数中的,而是进行thread构造,里面会有这些参数,因为是一个左值引用也就是进行一个深拷贝。thread的底层是pthread_create,需要特定类型的函数指针和参数:
在这里插入图片描述
所以为了可以保证一直是引用,可以使用ref()保证传值拷贝的时候是引用

在这里插入图片描述

	thread t1(Print, 10000 , ref(x) , ref(tex));thread t2(Print, 20000 , ref(x) , ref(tex));

这样就可以正常运行了!这里略显麻烦,为了适配底层的系统调用(C语言版本)需要付出一些代价!

我们再来看一个混合使用:lambda表达式,十分的优雅!
我们使用lambda表达式进行所有变量的引用捕捉,就能获取到x tex

	int x = 0;mutex tex;//捕捉所有的thread t1([&](){for (int i = 0; i < 10000; i++){tex.lock();x++;tex.unlock();}});thread t2([&](){for (int i = 0; i < 20000; i++){tex.lock();x++;tex.unlock();}});t1.join();t2.join();

优雅!

再来看:如果我们想要创建很多线程,但是先不使用。等待后续才进行使用。我们可以使用vector容器来进行储存线程,需要时就进行遍历来获取空的线程对象,对空的线程对象进行移动赋值!

int main()
{vector<thread> vthd;int n = 0;cin >> n;//进行初始化vthd.resize(n);int x = 0;mutex tex;auto func = [&](int n){for (int i = 0; i < n; i++){tex.lock();x++;tex.unlock();}};for (auto& thd : vthd){//移动赋值thd = thread(func, 10000);}for (auto& thd : vthd){thd.join();}cout << x << endl;return 0;
}

优雅!

1.4 锁

C++11中提供了很多种锁:
在这里插入图片描述
其中mutex中的接口有:

  1. lock:上锁 — 阻塞的,没锁可以使用就进行阻塞
  2. unlock:解锁
  3. try_lock:上锁 — 非阻塞的 ,没有锁可用就返回false

其中timed_mutex,在mutex的基础上加入了时间限定函数:

  1. try_lock_for :可以设置上锁的时间
  2. try_lock_until : 上锁到对应时间点

其中recursive_mutex递归锁,可以在递归函数中进行使用,防止死锁的问题!

实际用法到具体使用时在细细研究就好!

我们再来看一个比较巧妙的方法:

class LockGuard
{
public:LockGuard(mutex& mtx):_mtx(mtx){_mtx.lock();}~LockGuard(){_mtx.unlock();}
private:mutex& _mtx;
};

通过这个类,我们可以在临界区前创建一个锁守卫,生命周期结束就会自动解锁!为了不会锁住非临界区的数据,可以使用{ }划定局部域!库中提供了模版锁守卫lock_guard,可以方便使用!

2 原子操作

我们需要进行一些非原子操作的时候,比如++,或者修改一个全局的flag。使用锁操作有些大炮打蚊子的感觉,这时可以使用原子操作来进行!

在这里插入图片描述
其底层是cas先比较再设置,保证操作的原子性!
我们来试着使用一下:

atomic<int> x = 0;auto func = [&](int n){for (int i = 0; i < n; i++){x++;}};

这样就了可以保证操作的原子性了,比加锁简单多了!
获取其中的数据可以使用load接口,修改数据可以使用exchange接口…

3 条件变量

条件变量经常使用在多线程环境下,它允许线程在某些条件不满足时挂起(等待),直到另一个线程更新了共享数据并通知条件变量,使得等待的线程可以继续执行。条件变量主要提供以下接口:

  1. wait():阻塞当前线程,直到条件变量被唤醒,通常在互斥锁锁定的情况下调用,进入wait之前会进行一个解锁!
  2. wait_for():阻塞当前线程,直到条件变量被唤醒或给定的时间超时。
  3. wait_until():阻塞当前线程,直到条件变量被唤醒或到达某个特定的时间点。
  4. notify_one:通知一个线程开始工作,如果等待的超过1个,就会进行随机选择!
  5. notify_all:唤醒所有线程

我们来看一个例子:

我们来实现:两个线程交替打印奇偶数,我们来通过这个了解条件变量:
创建10个线程,都有对应1 - 10 的ID号,每次只能打印一个线程的id,如果ready为真就wait ,为假才继续进行。进行打印之前将ready设置为真,打印结束设置为假!
这里我们加入一个计时的接口this_thread::sleep_for(std::chrono::seconds( ))可以进行等待!

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variablestd::mutex mtx;
std::condition_variable cv;
bool ready = false;void print_id(int id) {std::unique_lock<std::mutex> lck(mtx);while (!ready) cv.wait(lck);// ...std::cout << "thread " << id << '\n';
}void go() {std::unique_lock<std::mutex> lck(mtx);ready = true;cv.notify_all();
}int main()
{//创建10个线程std::thread threads[10];// spawn 10 threads:for (int i = 0; i < 10; ++i)threads[i] = std::thread(print_id, i);std::cout << "10 threads ready to race...\n";//休眠保证所有线程进入waitthis_thread::sleep_for(std::chrono::seconds( 1 ))go();                       // go!for (auto& th : threads) th.join();return 0;
}

这样进行的打印就是随机的了,因为调用的顺序不确定!
在这里插入图片描述

  1. 我们需要创建两个线程来分别执行打印奇数和打印偶数
  2. 一定要保证一个线程打印了一个 ,另一个线程才能打印一个!此时就是一个类似进程间通信的场景,为 false 打印偶数 , 为 true 打印奇数!每次打印都进行调整状态,帮助按照顺序进行打印!
  3. 条件变量的作用是在变量不符合条件时进行阻塞,等待变量才进行!
int main()
{mutex mtx;condition_variable c;int n = 100;//为 false 打印偶数//为 true 打印奇数bool flag = false;thread t1([&](){int i = 0;while (i < n){unique_lock<mutex> lock(mtx);//只要flag == false就进行阻塞 //防止连续打印while (flag)c.wait(lock);cout << i << endl;flag = true;i += 2;c.notify_one();}});thread t2([&](){int i = 1;while (i < n){unique_lock<mutex> lock(mtx);//只要flag == true就进行阻塞while (!flag)c.wait(lock);cout << i << endl;flag = false;i += 2;c.notify_one();}});t1.join();t2.join();return 0;
}

来看效果:
在这里插入图片描述
很顺利!这时两个线程的情况,如果有多个进程,可以通过宏定义一些数字,每个线程任务对应一个数字。变量满足时才进行执行任务!这样就会让不符合条件的变量阻塞在条件变量或者阻塞在获取锁中!通过这样的调控,可以满足多线程情况下的并发需求!

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/394144.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

编译和汇编的区别

一、编译 编译是将高级语言&#xff08;如C、C、Java等&#xff09;编写的源代码转换成计算机可以直接执行的低级语言&#xff08;通常是机器语言或汇编语言&#xff09;的过程 编译 —— 将人类可读的源代码转换为计算机可执行的指令集 编译过程 通常包括词法分析、语法分…

正点原子imx6ull-mini-Linux驱动之Linux 网络驱动实验

网络驱动是 linux 里面驱动三巨头之一&#xff0c;linux 下的网络功能非常强大&#xff0c;嵌入式 linux 中也常 常用到网络功能。前面我们已经讲过了字符设备驱动和块设备驱动&#xff0c;本章我们就来学习一下 linux 里面的网络设备驱动。 1&#xff1a;嵌入式网络简介 1.1…

如何给源代码加密?这款加密软件教给你操作步骤

给源代码加密是保护软件知识产权和商业机密的重要手段。以这款加密软件安企神为例&#xff0c;给源代码加密的过程可以概述为以下几个方面。 一、安企神软件概述 安企神是一款功能强大的企业安全加密软件&#xff0c;它提供了全面的源代码防泄漏解决方案。该软件通过透明文件加…

扩散模型系列笔记(一)——DDPM

直观理解 扩散模型分为前向过程&#xff08;扩散过程&#xff0c;Data → \to →Noise&#xff09;和后向过程&#xff08;生成过程或逆扩散过程&#xff0c;Noise → \to →Data&#xff09;。在前向过程中&#xff0c;对于每一个观测样本&#xff0c;不断向样本中添加少量噪…

Leetcode每日刷题之字符串中的第一个唯一字符(C++)

在学习的过程中对代码的熟练运用至关重要&#xff0c;练习解决实际问题就可以很好的锻炼自己的编程能力&#xff0c;接下来让我们练习这道 387.字符串中的第一个唯一字符 思路解析 根据题意我们可以知道这个字符串只有小写字母&#xff0c;并且可能包含多个唯一字符&#xff0…

Java---字符串string练习

目录&#xff1a; 1.将数字转换成罗马数字 2.键盘输入任意字符串&#xff0c;打乱里面的内容 3.返回字符串中最后一个单词长度 4.调整A字符串 看是否可与B字符串匹配 一&#xff1a; //键盘录入一个字符串// 长度小于等于9 只能是数字// -将内容变成罗马数字// Ⅰ Ⅱ Ⅲ Ⅳ…

智慧水务项目(二)django(drf)+angular 18 创建通用model,并对orm常用字段进行说明

一、说明 上一篇文章建立一个最简单的项目&#xff0c;现在我们建立一个公共模型&#xff0c;抽取公共字段&#xff0c;以便于后续模块继承&#xff0c;过程之中会对orm常用字段进行说明&#xff0c;用到的介绍一下 二、创建一个db.py 目录如下图 1、代码 from importlib im…

基于QT实现的简易WPS(已开源)

一、开发工具及开源地址&#xff1a; 开发工具&#xff1a;QTCreator &#xff0c;QT 5 开源地址&#xff1a; GitHub - Whale-xh/WPS_official: Simple WPS based on QTSimple WPS based on QT. Contribute to Whale-xh/WPS_official development by creating an acc…

推荐 3个实用且完全免费的在线工具,每天都会用到,无需登录打开即用

100font 100font是一个专业的免费商用字体下载网站&#xff0c;专注于收集、整理和分享各种免费无版权的商用字体。用户可以在这个平台上找到并下载简体中文、繁体中文、英文、日文、韩文等多种语言类型的字体。 该网站的特点包括清晰的分类和直观的下载流程&#xff0c;用户可…

进阶SpringBoot之 Spring 官网或 IDEA 快速构建项目

SpringBoot 就是一个 JavaWeb 的开发框架&#xff0c;约定大于配置 程序 数据结构 算法 微服务架构是把每个功能元素独立出来&#xff0c;再动态组合&#xff0c;是对功能元素的复制 这样做可以节省调用资源&#xff0c;每个功能元素的服务都是一个可替代、可独立升级的软…

算法混合杂项

基础类型 可用template 投影 是有方向的 求俩直线交点 推公式 q我们不知道&#xff0c;已知p1 p2&#xff0c;正弦定理&#xff0c;α可以用叉积表示出来 β同理 所以我们能求出p1q 已知piq 回归到我们上一个问题&#xff0c;已知方向和长度&#xff0c;我们就能够求出Voq …

C语言 ——— 学习并使用字符分类函数

目录 学习isupper函数 学习isdigit函数 学习tolower函数 将输入的字符串中把大写字母转换为小写字母并输出 学习isupper函数 参数部分&#xff1a; 形参需要传递的是一个字母&#xff0c;字符在ASCII码表上是以整型存储的&#xff0c;所以实参部分用(int c)没有问题 返回…

【iOS】AutoreleasePool自动释放池的实现原理

目录 ARC与MRC项目中的main函数自动释放池autoreleasepool {}实现原理AutoreleasePoolPage总结 objc_autoreleasePoolPush的源码分析autoreleaseNewPageautoreleaseFullPageautoreleaseNoPage autoreleaseFast总结 autorelease方法源码分析objc_autoreleasePoolPop的源码分析po…

谁来做引领企业精益变革的舵手最合适?

在这个瞬息万变的商业时代&#xff0c;企业如同航行在波涛汹涌的大海中的巨轮&#xff0c;既需面对未知的挑战&#xff0c;也要抓住稍纵即逝的机遇。而在这场没有终点的航行中&#xff0c;引领企业实现精益变革的舵手&#xff0c;无疑是推动企业破浪前行、稳健致远的关键角色。…

FFmpeg Windows安装教程

一. 下载ffmpeg 进入Download FFmpeg网址&#xff0c;点击下载windows版ffmpeg。 下载第一个essentials版本就行。 二. 环境配置 上面源码解压后如下 将bin添加到系统环境变量 验证安装是否成功&#xff0c;输入ffmpeg –version&#xff0c;显示版本即为安装成功。

Python学习(1):使用Python的Dask库实现并行计算

目录 一、Dask介绍 二、使用说明 安装 三、测试 1、单个文件中实现功能 2、运行多个可执行文件 最近在写并行计算相关部分&#xff0c;用到了python的Dask库。 Dask官网&#xff1a;Dask | Scale the Python tools you love 一、Dask介绍 Dask是一个灵活的并行和分布式…

网工内推 | 国企运维工程师,华为认证优先,最高年薪20w

01 上海陆家嘴物业管理有限公司 &#x1f537;招聘岗位&#xff1a;IT运维工程师 &#x1f537;岗位职责&#xff1a; 1、负责对公司软、硬件系统、周边设备、桌面系统、服务器、网络基础环境运行维护、故障排除。 2、负责对各部门软件操作、网络安全进行检查、指导。 3、负责…

Mysql——update更新数据的方式

注&#xff1a;文章参考&#xff1a; MySQL 更新数据 不同条件(批量)更新不同值_update批量更新同一列不同值-CSDN博客文章浏览阅读2w次&#xff0c;点赞20次&#xff0c;收藏70次。一般在更新时会遇到以下场景&#xff1a;1.全部更新&#xff1b;2.根据条件更新字段中的某部分…

vivado OPT_SKIPPED

当跳过候选基元单元的逻辑优化时&#xff0c;OPT_skipped属性 更新单元格以反映跳过的优化。当跳过多个优化时 在同一单元格上&#xff0c;OPT_SKIPPED值包含跳过的优化列表。 架构支持 所有架构。 适用对象 OPT_SKIPPED属性放置在单元格上。 价值观 下表列出了各种OPT_design选…

【CSDN平台BUG】markdown图片链接格式被手机端编辑器自动破坏(8.6 已修复)

文章目录 bug以及解决方法bug原理锐评后续 bug以及解决方法 现在是2024年8月&#xff0c;我打开csdn手机编辑器打算修改一下2023年12月的一篇文章&#xff0c;结果一进入编辑器&#xff0c;源码就变成了下面这个样子&#xff0c;我起初不以为意&#xff0c;就点击了发布&#…