【C++11】线程

本篇文章更多的是熟悉一下C++11的线程库接口,与linux的相关线程接口是非常相似的,更多的是将面向过程改为了面向对象。
并没有一些概念的讲解。
想知道线程的相关概念的可以看一看这篇文章及后续

在C++11之前,涉及到多线程问题,都是和平台相关的,
比如windows和linux下各有自己的接口,这使得代码的可移植性比较差。
C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含< thread >头文件。

目录

  • 线程接口:
    • 构造:
    • 常用接口:
    • 线程函数参数:
    • 具体实践:
      • 创建线程的方法:
      • 创建多线程的方法:
    • this_thread:
      • get_id:
      • yeild:
  • 互斥锁:
    • 锁的接口:
    • 锁的使用:
    • 其他的锁:
    • lock_guard:
    • unique_lock:
  • 条件变量:

线程接口:

构造:

首先我们来看构造,他有无参构造,带参构造(模板+万能引用+多参数模板),移动构造,却没有拷贝构造,
在这里插入图片描述
没有拷贝构造的原因在于在这里插入图片描述

常用接口:

在这里插入图片描述
注意:

  1. 线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。
  2. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。
#include <thread>
int main()
{std::thread t1;cout << t1.get_id() << endl;return 0;
}

get_id()的返回值类型为id类型,id类型实际为std::thread命名空间下封装的一个类,该类中包含了一个结构体:

// vs下查看
typedef struct
{ /* thread identifier for Win32 */void *_Hnd; /* Win32 HANDLE */unsigned int _Id;
} _Thrd_imp_t;
  1. 当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。
    线程函数一般情况下可按照以下三种方式提供:
    -函数指针
    -lambda表达式
    -仿函数
  2. thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
  3. 可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效:
  • 采用无参构造函数构造的线程对象
  • 线程对象的状态已经转移给其他线程对象
  • 线程已经调用jion或者detach结束

线程函数参数:

线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。

void ThreadFunc1(int& x)
{x += 10;
}
void ThreadFunc2(int* x)
{*x += 10;
}
int main()
{int a = 10;// 在线程函数中对a修改,不会影响外部实参,因为:线程函数参数虽然是引用方式,但其实际引用的是线程栈中的拷贝thread t1(ThreadFunc1, a);t1.join();cout << a << endl;// 如果想要通过形参改变外部实参时,必须借助std::ref()函数thread t2(ThreadFunc1, std::ref(a);t2.join();cout << a << endl;// 地址的拷贝thread t3(ThreadFunc2, &a);t3.join();cout << a << endl;return 0;
}

那我们怎么理解多个线程执行同一个函数,因为每个线程都有独立的栈,所以虽然是同一个函数,却在不同的栈建立栈帧。

具体实践:

创建线程的方法:

由于可以使用lambda,我们选择直接使用lambda作为线程函数。

int main()
{thread t1([](int x)->void{for (int i = 0; i < x; i++){cout << " I am Thraed-1" << endl;}}, 10);thread t2([](int x)->void{for (int i = 0; i < x; i++){cout << " I am Thraed-2" << endl;}}, 20);t1.join();t2.join();return 0;
}

创建多线程的方法:

上段代码过于冗余,如果我们要创建更多但是功能相近的线程函数,该如何操作呢?
使用vector + thread无参构造即可轻松管理多线程。

在这里插入图片描述
注意:这里有一个细节,我们在循环体内应慎用lambda全部引用捕捉,如果上图代码采取引用捕捉,由于我们在lambda中使用了i这个for循环内部的控制变量,有可能还没有开始执行lambda,但是循环却已经结束,i到了5,导致数组越界从而报错。

this_thread:

为啥要看这个东西?因为很有用!
注意:这是std作用域中的this_thread域,里面有一些如下函数:
在这里插入图片描述

在这里插入图片描述
我们在创建线程时,如果需要有在lambda内打印id的需求,我们是没有办法打印出来的,即使我们的thread提供了get_id函数。在这里插入图片描述
这是因为t1对象还没有构建好。

那我们怎么获取?
就在这个命名空间内,有以上4个函数。

get_id:

在这里插入图片描述
这样即可在构建时得到对应的id。

yeild:

对于这个接口我们目前了解一下即可。

这是啥呢?
这是让出当前线程时间片的一个接口(对于OS的一个建议)。
例子:

假设你正在一个繁忙的办公室工作,你和同事都需要使用同一台打印机。为了公平起见,你们约定每个人每次只能使用打印机一分钟。现在,你正在打印一个文件,但你发现文件很长,一分钟内肯定打印不完。这时,你看到旁边有几位同事也在等着打印急件,他们看起来比你还着急。
于是,你决定做一个礼貌的举动:你暂时放下自己的打印任务,让出打印机给其他人使用。这就像是在说:“我还有很多页要打印,但我可以先等等,你们先用吧。”这就是std::this_thread::yield函数在多线程环境中的作用——它让当前线程“礼让”,给其他线程一个运行的机会。

一般在无锁编程(无锁编程指的是少用锁,因为效率低)中出现。那么这就要提一提CAS(cmpare_and_swap)了。

也就是原子操作。
比如我们的i++在汇编层面是至少3条语句:(拿出i,进行修改,放回内存)。
这就导致在多线程时出现线程安全,于是就衍生出了原子操作,这是由硬件直接支持好的,也就是只有一条汇编语句,不存在线程安全的问题,

对于线程安全来说,软件层面有很多方式,比如锁,原子操作…但是这些各种各样的方式都离不开硬件的支持。

这两种方法我们随后都会涉及到。

关于sleep_until是休眠到一个绝对时间,
而sleep_for是休眠你指定的时间,原理是比较复杂的,需要的时候直接看文档copy代码即可。

互斥锁:

当我们对一个全局变量i进行多线程++时,线程安全就毫无意外的出现了。

所以我们需要进行加锁。

锁的接口:

这里我们只关注常用的lock与unlock即可

锁是不可拷贝的。
在这里插入图片描述
注意,线程函数调用lock()时,可能会发生以下三种情况:

  • 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁
  • 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住
  • 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)

线程函数调用try_lock()时,可能会发生以下三种情况:

  • 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock释放互斥量
  • 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉
  • 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)

锁的使用:

注意放锁的位置即可。

int x = 0;
mutex mtx;
void func(int n)
{for (int i = 0; i < n; i++){mtx.lock();x++;mtx.unlock();}
}int main()
{size_t begin = clock();thread t1(func, 100000);thread t2(func, 500000);t1.join();t2.join();size_t end = clock();cout << x << endl;cout << end - begin << endl;return 0;
}

但是注意:
上下段两段代码哪个效率高?

void func(int n)
{mtx.lock();for (int i = 0; i < n; i++){x++;}mtx.unlock();
}

答案是下段高,为什么下段明明是串行却比上段的部分串行效率高?
在这里插入图片描述
这与我们学习到的要尽可能的细粒度加锁不太对劲啊。

原因有二:

  1. 频繁的lock与unlock
  2. i++太快了,当t1线程抢到锁,t2刚去阻塞,但是由于++太快了,导致刚去阻塞就又回来了,导致频繁的上下文切换。

其他的锁:

std::recursive_mutex

其允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。

std::timed_mutex

比 std::mutex 多了两个成员函数,try_lock_for(),try_lock_until() 。
for是锁你指定的时间,until是锁到你指定的时间。

std::recursive_timed_mutex

上述两个锁的特性相加。

lock_guard:

template<class _Mutex>
class lock_guard
{
public:// 在构造lock_gard时,_Mtx还没有被上锁explicit lock_guard(_Mutex& _Mtx): _MyMutex(_Mtx){_MyMutex.lock();}// 在构造lock_gard时,_Mtx已经被上锁,此处不需要再上锁lock_guard(_Mutex& _Mtx, adopt_lock_t): _MyMutex(_Mtx){}~lock_guard() _NOEXCEPT{_MyMutex.unlock();}lock_guard(const lock_guard&) = delete;lock_guard& operator=(const lock_guard&) = delete;
private:_Mutex& _MyMutex;
};

通过上述代码可以看到,lock_guard类模板主要是通过RAII的方式,对其管理的互斥量进行了封装,在需要加锁的地方,只需要用上述介绍的任意互斥体实例化一个lock_guard,调用构造函数成功上锁,出作用域前,lock_guard对象要被销毁,调用析构函数自动解锁,可以有效避免死锁问题。

lock_guard的缺陷:太单一,用户没有办法对该锁进行控制,因此C++11又提供了unique_lock。

unique_lock:

与lock_gard类似,unique_lock类模板也是采用RAII的方式对锁进行了封装,并且也是以独占所有权的方式管理mutex对象的上锁和解锁操作,即其对象之间不能发生拷贝。

在构造(或移动(move)赋值)时,unique_lock 对象需要传递一个 Mutex 对象作为它的参数,新创建的unique_lock 对象负责传入的 Mutex 对象的上锁和解锁操作。使用以上类型互斥量实例化

unique_lock的对象时,自动调用构造函数上锁,unique_lock对象销毁时自动调用析构函数解锁,可以很方便的防止死锁问题。

与lock_guard不同的是,unique_lock更加的灵活,提供了更多的成员函数:

  • 上锁/解锁操作:lock、try_lock、try_lock_for、try_lock_until和unlock
  • 修改操作:移动赋值、交换(swap:与另一个unique_lock对象互换所管理的互斥量所有权)、释放(release:返回它所管理的互斥量对象的指针,并释放所有权)
  • 获取属性:owns_lock(返回当前对象是否上了锁)、operator bool()(与owns_lock()的功能相同)、mutex(返回当前unique_lock所管理的互斥量的指针)。

条件变量:

持续更新~

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

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

相关文章

访问控制系列

目录 一、基本概念 1.客体与主体 2.引用监控器与引用验证机制 3.安全策略与安全模型 4.安全内核 5.可信计算基 二、访问矩阵 三、访问控制策略 1.主体属性 2.客体属性 3.授权者组成 4.访问控制粒度 5.主体、客体状态 6.历史记录和上下文环境 7.数据内容 8.决策…

使用TableConvert API将CSV转换为JSON数组

TableConvert API 是一款多功能工具&#xff0c;旨在简化不同数据格式之间的转换过程。通过访问370种不同的转换器&#xff0c;该API可以在包括CSV、Excel、HTML、JSON、Markdown等多种文件类型和结构之间实现无缝数据转换。 为什么选择TableConvert的CSV到JSON数组API&#x…

面试问题:react的Reconciler(调度器)为什么在做异步可中断不用原生Generator,自己做了一个Fiber

首先Generator也是有异步中断功能的但是能他是有传染性的&#xff0c;使用了Generator则需要上下文的其他函数也需要做主改变&#xff0c;这样心智负担比较重&#xff0c;就比如说我定义一个Generator方法&#xff0c;里面有ABC三个函数我分别在B的前面和C的前面放一个yield打断…

Linux: network: device事件注册机制 chatGPT; notify

ChatGPT 在 Linux 内核中,有关网络设备(net-device)的事件注册机制,允许用户在网络设备的状态发生变化(例如设备被删除、添加或修改)时接收通知。这主要通过 netdev 事件通知机制实现。具体来说,内核提供了一组用于注册和处理网络设备事件的 API。 以下是一些关键组件…

memcached 高性能内存对象缓存

memcached 高性能内存对象缓存 memcache是一款开源的高性能分布式内存对象缓存系统&#xff0c;常用于做大型动态web服务器的中间件缓存。 mamcached做web服务的中间缓存示意图 当web服务器接收到请求需要处理动态页面元素时&#xff0c;通常要去数据库调用数据&#xff0c;但…

【快速逆向一/无过程/有源码】《大学》在线投稿系统

逆向日期&#xff1a;2024.07.18 使用工具&#xff1a;Node.js 加密工具&#xff1a;Crypto-js标准库 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 【点赞 收藏 关注 】仅供学习&#xff0c;仅供学习&#xff0c; 本文为快速逆向&#x…

如果制作红星照耀中国思维导图?6个软件帮助你快速制作思维导图

如果制作红星照耀中国思维导图&#xff1f;6个软件帮助你快速制作思维导图 制作《红星照耀中国》思维导图可以帮助更好地理解和梳理书中的重要信息和内容。以下是六款推荐的思维导图软件及其特点和使用方法&#xff0c;帮助你快速制作高质量的思维导图。 迅捷画图 特点与功…

Milvus核心组件(2)---- etcd 详解

目录 背景 etcd 简介 1. 基本概念 2. 数据存储特性 3. KVS的操作 4. 租约(Lease)机制 5. 实际应用场景 Milvus 下的 etcd 服务及存储结构 etcd 服务 端口 存储位置 安全连接信息 嵌入式方式运行 etcd 文件存储结构 解析etcd 文件 连接 etcd server 注意事项…

n2. Web相关知识和工具

Web相关知识和工具 1. http协议相关基础知识2. http协议状态码3. Web相关工具2.1 links2.2 wget2.3 curl2.4 httpie 4. httpd的压力测试工具 1. http协议相关基础知识 URI&#xff1a; Uniform Resource Identifier 统一资源标识&#xff0c;分为URL 和 URN URN&#xff1a;U…

Python基础语法篇(下)+ 数据可视化

Python基础语法&#xff08;下&#xff09; 数据可视化 一、函数&#xff08;一&#xff09;函数的定义&#xff08;二&#xff09;函数的调用和传参 二、文件操作&#xff08;一&#xff09;文件读取和写入&#xff08;二&#xff09;文件对象及方法&#xff08;三&#xff09…

【数学建模】——【线性规划】及其在资源优化中的应用

目录 线性规划问题的两类主要应用&#xff1a; 线性规划的数学模型的三要素&#xff1a; 线性规划的一般步骤&#xff1a; 例1&#xff1a; 人数选择 例2 &#xff1a;任务分配问题 例3: 饮食问题 线性规划模型 线性规划的模型一般可表示为 线性规划的模型标准型&…

达梦数据库的系统视图v$sqltext

达梦数据库的系统视图v$sqltext 在达梦数据库&#xff08;DM Database&#xff09;中&#xff0c;V$SQLTEXT 是一个系统视图&#xff0c;用于显示当前正在执行或最近执行的SQL语句的文本信息。这个视图对于监控和分析数据库中的SQL活动非常有用&#xff0c;尤其是在需要调试性…

【MySQL篇】Percona XtraBackup工具备份指南:常用备份命令详解与实践(第二篇,总共五篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…

银河麒麟搭建ftp服务器

1.先 查看系统架构&#xff0c;我常遇到的一般银河麒麟是arrch64的 lscpu uname -a cat /etc/os-release 去下载对应版本的vsftp.rpm包和ftp包 Index of /NS/ (cs2c.com.cn) 1.安装rpm rpm -ivh *.rpm --nodeps --force #强制安装 2.修改配置文件 vi /etc/vsftpd/vsftpd.co…

Qt Android Native Error: JNI DETECTED ERROR IN APPLICATION: java_object == null

开发的qt android程序在低版本上运行正常&#xff0c;在高版本上启动时崩溃&#xff0c;报如下错误 W java.lang.RuntimeException: Cant create handler inside thread Thread[qtMainLoopThread,5,main] that has not called Looper.prepare()at android.os.Handler.<ini…

如何使用Python调用颜值评分接口

引言 在当今社会&#xff0c;人工智能技术被应用于各个领域&#xff0c;包括图像识别和分析。今天&#xff0c;我们将利用Python来调用小思框架颜值评分接口&#xff0c;该接口可以接收一张人脸图片&#xff0c;并返回一个表示颜值水平的分数。 接口功能与参数 方法URL参数描…

PiT : 基于池化层Pooling layer的Vision Transformer

CNN的降维原理;随着深度的增加,传统CNN的通道维数增加,空间维数减少。经验表明,这样的空间降维对变压器结构也是有益的,并在原有的ViT模型的基础上提出了一种新的基于池的视觉变压器(PiT)。 1. 引言 ViT与卷积神经网络(CNN)有很大的不同。将输入图像分成1616小块馈送到变压…

C++ 匹配并提取包括加中括号的日期时间的正则表达式

在C中&#xff0c;你可以使用std::regex库来匹配包含日期和时间的字符串。以下是一个简单的例子&#xff0c;它展示了如何使用正则表达式来匹配形如[YYYY-MM-DD HH:MM:SS]的字符串。include <iostream> #include <string> #include <regex> int main() { …

学懂C#编程:精通C#、.NET开发之核心编程知识学习指南

无论你是编程新手&#xff0c;还是想要深化.NET技能的开发者&#xff0c;本文都将为你提供一条清晰的学习路径&#xff0c;从C#基础到高级特性&#xff0c;每一站都配有详尽解析和实用示例&#xff0c;旨在帮助你建立坚实的知识体系&#xff0c;并激发你对C#及.NET生态的热情。…

LabVIEW软件开发的雷区在哪里?

在LabVIEW软件开发中&#xff0c;有几个需要注意的雷区&#xff0c;以避免常见的错误和提高开发效率&#xff1a; 1. 不良的代码结构 雷区&#xff1a;混乱的代码结构和不清晰的程序逻辑。 后果&#xff1a;导致难以维护和调试的代码&#xff0c;增加了错误和故障的风险。 …