C++11线程库简介

前言

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

目录

1.thread类的简单介绍

2.线程函数参数

3.原子性操作库

4.lock_guard和unique_lock

        4.1mutex的种类

        4.2lock_guard

 4.3unique_lock 

5.例子 


1.thread类的简单介绍

        线程类 

         

thread类成员函数说明
函数名功能
thread()      构建一个线程对象,没有关联任何线程函数,即没有启动任何线程 
thread(fn,args1,args2...)构建一个线程对象,并关联线程函数fn,args1        ,args2,...为线程函数的参数
get_id()获取线程的id
jionable()线程是否还在执行,jionable代表一个正在执行中的线程
jion()该函数调用后会阻塞住线程,该线程执行结束后,主线程继续执行。
detach()在创建线程对象后马上调用,用于把被创建的线程与线程对象分离,分离后的线程变为后台线程,创建的线程“死活”就与主线程无关了

        注意:

        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; 

        3.当创建一个线程对象后,并且给出线程关联的线程函数,该线程就被启动了,与主线程一起运行。线程函数一般情况下可以按照以下三种提供方式。

        1.函数指针

        2.lambda表达式

        3.函数对象 

        

void ThreadFunc(int a)
{cout << "ThreadFunc" << a << endl;
}
class TF
{
public:void operator()(){cout << "Thread3" << endl;}
};
#include<thread>
int main()
{//线程函数为函数指针std::thread t1(ThreadFunc,10);//线程函数为lambda表达式thread t2([]() {cout << "Thread2" << endl;});//线程函数为函数对象TF tf;thread t3(tf);t1.join();t2.join();t3.join();cout << "Mian thread" << endl;return 0;
}

         4.thread类是防拷贝的,不允许拷贝构造和赋值,但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象,转移期间不影响线程的执行。

        5.可以通过判断joinable()函数,判断线程是否有效,如果在一下任意情况下,则线程是无效的

        采用无参构造函数构造的线程对象

        线程对象的状态已经转移给其它对象

        线程已经调用join或者detach结束

2.线程函数参数

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

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

         注意:如果是类的成员函数作为线程参数的时候,必须将this指针作为线程函数参数。

3.原子性操作库

        多线程最主要的问题就是数据共享带来的问题(即线程安全的问题)。如果共享数据都是只读的,那么没有什么问题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有的线程都会获得同样的数据。但是,当一个或者多个线程要对数据进行修改共享数据的时候,就会产生很多潜在的麻烦。比如:

#include<thread>
#include<iostream>
using namespace std;
unsigned long sum = 0L;int main()
{cout << "Before joining,sum = " << sum << std::endl;int sum = 0;thread t1([&](int num) {for (int i = 0; i < num; ++i){++sum;}}, 10000000);thread t2([&](int num) {for (int i = 0; i < num; ++i){++sum;}}, 10000000);t1.join();t2.join();cout << "After joining,sum = " << sum << std::endl;return 0;
}

        这样加起来的结果是不准确的。

        C++98中会采取传统的方式解决:可以对共享修改的数据进行加锁保护。

#include<thread>
#include<iostream>
#include<mutex>
using namespace std;
unsigned long sum = 0L;int main()
{std::cout << "Before joining,sum = " << sum << std::endl;int sum = 0;mutex mu;thread t1([&](int num) {for (int i = 0; i < num; ++i){mu.lock();//加锁++sum;mu.unlock();//解锁}}, 10000000);thread t2([&](int num) {for (int i = 0; i < num; ++i){mu.lock();//加锁++sum;mu.unlock();//解锁}}, 10000000);t1.join();t2.join();std::cout << "After joining,sum = " << sum << std::endl;return 0;
}

        虽然加锁可以解决这个问题,但是代码的运行效率会很慢,而且如果一个线程正在访问sum其他想要访问sum的线程就会被阻塞。会影响程序的运行效率。而且如果锁控制不好,还容易造成死锁。

        因此在C++11中 引入了原子操作。所谓的原子操作:即不可被中断的一个或者一系列的操作。C++11引入原子操作类型,使得线程间的数据同步变得非常高效。

        注意:使用以上的原子操作变量的时候,必须添加头文件。<atomic>

int main()
{std::cout << "Before joining,sum = " << sum << std::endl;atomic_int sum = 0;//原子类型的变量summutex mu;thread t1([&](int num) {for (int i = 0; i < num; ++i){++sum;}}, 10000000);thread t2([&](int num) {for (int i = 0; i < num; ++i){++sum;}}, 10000000);t1.join();t2.join();std::cout << "After joining,sum = " << sum << std::endl;return 0;
}

         在C++11中,程序员不需要对原子类型的变量进行加锁解锁的操作,线程能够对原子变量互斥访问。

        更为普遍的是,程序员可以使用atomic类型的模版,定义出需要的任意原子类型。

        atomic<T> t;//声明一个类型为T的原子变量类型

         注意:原子类型通常属于“资源型数据”,多个线程只允许访问单个原子类型的拷贝,因此在C++11中,原子类型只能从模版参数中进行构造,不允许原子类型进行拷贝构造,移动构造以及operator=等,为了防止意外,标准库已经将atomic模版类中拷贝构造,赋值运算符重载,移动构造全都进行了删除。

int main()
{atomic<int> a1;//会报错atomic<int> a2(a1);atomic<int> a3;a3 = a1;return 0;
}

4.lock_guard和unique_lock

        在多线程的环境, 如果想要保证某个变量的安全性,只要将其设置为对应的原子类型就可以了,高效又不容易出现死锁的问题。但是在某些情况下:我们可能需要保证一段代码的安全性,那么就需要通过锁的方式来进行控制。

        比如一个线程对变量number进行加1 ,100次,另外一个-1,100次,每次加减以后都要输出number的值。要求最后number的值为1;

int main()
{int number = 1;mutex mu;thread t1([&](int num) {for (int i = 0; i < num; ++i){mu.lock();++number;cout << number << endl;mu.unlock();}}, 100);thread t2([&](int num) {for (int i = 0; i < num; ++i){mu.lock();--number;cout << number << endl;mu.unlock();}}, 100);t1.join();t2.join();cout << number << endl;
}

        上述代码存在一定的缺陷:如果锁控制不好,可能会造成死锁,最常见的比如在锁的中间返回,或者还没有解锁就抛异常了,因此C++11采用RALL的方式对锁进行封装,即lock_guardunique_lock。 

        4.1mutex的种类

        在C++11中mutex总共包含四种互斥量的种类:

        1.std::mutex

        C++11提供的最基本的互斥量,该类的对象之间不能互相拷贝,也不能进行移动 ,mutex最常用的三个函数:

函数声明函数功能
lock()        加锁
unlock()解锁
try_lock()尝试锁住互斥量,如果互斥量被其它线程占有,则当前线程也不会被阻塞

        注意:

        线程函数调用lock()时,可能会出现一下三种情况:

        1.如果该互斥量没有被锁住,则线程会将该互斥量锁住,直到调用unlock()之前,该线程都是有锁的。

        2.如果当前互斥量被其他线程锁住,则当前的调用线程会被阻塞。

        3.如果当前互斥量已经被其他线程锁住则会造成死锁。

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

        1.如果当前互斥量没有被其它线程占用,则该线程锁住该互斥量,直到该线程调用unlock()解锁释放互斥量。

        2.如果当前被其它线程占用,则调用线程返回false,并且不会阻塞。

        3.如果当前互斥量被当前线程锁住,则会造成死锁

        2.std::recursive_mutex

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

        3.std::timed_mutex

        比std::mutex 多了两个成员,try_lock_for(),try_lock_until()。

        try_lock_for: 

        接受一个时间范围,表示在这段时间范围之内如果线程如果没有获得锁则被阻塞住,与std::mutex的try_lock不同,try_lock如果被调用时没有获得锁直接返回false,如果在此期间其它线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定的时间内没有获得锁),则返回false。

        try_lock_until()

        接收一个时间点作为参数,在指定的时间点未到来之前线程如果没有获得锁则被阻塞,如果在此期间其它线程释放了锁,则该线程可以获得互斥量的锁,如果超时(即在指定的饿时间内还没有获得锁),则返回false。

        4.std::recursive_timed_mutex 

        4.2lock_guard

        std::lock_guard时C++中定义的模版类,定义如下:

    template<class Mutex>class lock_guard{public://构造之前mu还未加锁explicit lock_guard(Mutex& mu):_mu(mu){_mu.lock();}// 在构造lock_gard时,_Mtx已经被上锁,此处不需要再上锁lock_guard(_Mutex& _Mtx, adopt_lock_t): _MyMutex(_Mtx){}//析构函数进行解锁~lock_guard(){_mu.unlock();}//不允许拷贝构造和赋值lock_guard(const Mutex& mu) = delete;Mutex& operator()(const Mutex& mu) = delete;private:Mutex & _mu;};

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

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

 4.3unique_lock 

        与lock_gard类似,unique_lock类模板也采用 RAII的方式对锁将进行封装,并且也是以独占所有权的方式管理mutex对象的上锁和解锁操作,即其对象之间不能发生拷贝,在构造(或者移动(move)赋值时),unique_lock对象需要传递一个Mutex对象作为它的参数,新创建的unique_lock对象负责传入一个Mutex对象的上锁和解锁操作。使用以上类型互斥量实例化unique_lock的对象时,自动调用构造函数上锁,unique_lock对象销毁自动调用析构函数对锁进行解锁。可以很方便防止死锁问题。

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

        1.上锁/解锁操作:lock、try_lock,try_lock_for,try_lock_until和unlock。

        2.修改操作:移动赋值,交换(swap:与另一个unique_lock对象互换所管理的互斥量所有权),释放(release:返回它所管理的互斥量对象的指针,并释放所有权)

        3.获取属性:owns_lock(返回当前对象是否上锁),operator bool()(与owns_lock()功能相同),mutex(返回当前unique_lock所管理的互斥量的指针)。

5.例子 

        支持两个线程交替打印,一个打印奇数,一个打印偶数。

        主要是依靠条件变量加互斥量来实现的。 

#include<condition_variable>
int main()
{int n = 1000;//创建两个锁和两个条件变量mutex mu1;mutex mu2;condition_variable cv1;condition_variable cv2;thread t1([&]() {for (int i = 0; i < n; i+=2){unique_lock<mutex>mut1(mu1);cout << i << endl;//打印之后通知另一个进程,然后自己也等待通知cv2.notify_one();cv1.wait(mut1);}});thread t2([&]() {for (int i = 1; i < n; i += 2){unique_lock<mutex>mut2(mu2);//等待进程的通知cv2.wait(mut2);cout << i << endl;//打印完后通知另一个进程cv1.notify_one();}});t1.join();t2.join();return 0;
}

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

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

相关文章

LeetCode:3. 无重复字符的最长子串

给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 3. 无重复字符的最长子串 - 力扣&#xff08;LeetCode&#xff09; // 3.无重复字符的最长子串 // 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 class Solu…

DETR:End-to-End Object Detection with Transformers

代码&#xff1a;https://github.com/HuKai97/detr-annotations 论文&#xff1a;https://arxiv.org/pdf/2005.12872.pdf 参考视频&#xff1a;DETR 论文精读【论文精读】_哔哩哔哩_bilibili 团队&#xff1a;Meta AI 摘要 DETR 做目标检测任务既不需要proposal&#xff0…

elasticsearch4-文档操作

个人名片&#xff1a; 博主&#xff1a;酒徒ᝰ. 个人简介&#xff1a;沉醉在酒中&#xff0c;借着一股酒劲&#xff0c;去拼搏一个未来。 本篇励志&#xff1a;三人行&#xff0c;必有我师焉。 本项目基于B站黑马程序员Java《SpringCloud微服务技术栈》&#xff0c;SpringCloud…

html的日期选择插件

1.效果 2.文档 https://layui.gitee.io/v2/docs/ 3.引入 官网地址&#xff1a; https://layui.gitee.io/v2/ 引入&#xff08;在官网下载&#xff0c;&#xff09;jquery-1.7.2.min.js,layui/layui.js **<link href"js/layui/css/layui.css" rel"stylesh…

哈夫曼编码原理及实现

文章目录 一.哈夫曼编码原理哈夫曼二叉树构建 二.具体代码实现 一.哈夫曼编码原理 哈夫曼编码&#xff08;Huffman Coding&#xff09;是一种用于数据压缩的编码方法&#xff0c;它通过给出不同的数据符号分配不同长度的编码&#xff0c;使得出现频率高的符号具有较短的编码&a…

OpenCV(四十一):图像分割-分水岭法

1.分水岭方法介绍 OpenCV 提供了分水岭算法&#xff08;Watershed Algorithm&#xff09;的实现&#xff0c; 使用分水岭算法对图像进行分割&#xff0c;将图像的不同区域分割成互不干扰的区域。分水岭算法模拟了水在图像中的扩散和聚集过程&#xff0c;将标记的边界被看作是阻…

PHP8中获取并删除数组中第一个元素-PHP8知识详解

我在上一节关于数组的教程&#xff0c;讲的是在php8中获取并删除数组中最后一个元素&#xff0c;今天分享的是相反的&#xff1a;PHP8中获取并删除数组中第一个元素。 回顾一下昨天的知识&#xff0c;array_pop()函数将返回数组的最后一个元素&#xff0c;今天学习的是使用arr…

Vue自动生成二维码并可下载二维码

遇到一个需求&#xff0c;需要前端自行生成用户的个人名片分享二维码&#xff0c;并提供二维码下载功能。在网上找到很多解决方案&#xff0c;最终吭哧吭哧做完了&#xff0c;把它整理记录一下&#xff0c;方便后续学习使用&#xff01;嘿嘿O(∩_∩)O~ 这个小东西有以下功能特点…

AWT中常用组件

笔记&#xff1a;https://www.yuque.com/huangzhanqi/rhwoir/repuodh23fz01wiv 仓库&#xff1a;Java图形化界面: Java图形化界面学习demo与资料 (gitee.com) 基本组件 组件名 功能 Button Button Canvas 用于绘图的画布 Checkbox 复选框组件&#xff08;也可当做单选…

批量获取CSDN文章对文章质量分进行检测,有助于优化文章质量

&#x1f4da;目录 ⚙️简介✨分析获取步骤⛳获取文章列表☘️前期准备✨ 接口解析⚡️ 获取文章的接口 ☄️文章质量分接口⭐接口分析 ⌛代码实现&#xff1a;⚓核心代码:⛵测试用例:⛴ 运行效果:☘️增加Excel导出 ✍️结束 ⚙️简介 有时候我们写文章是为了记录当下遇到的bu…

查看表结构

MySQL从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129334507?spm1001.2014.3001.5502 语法格式: desc 表名; 描述: 如果表不存在,就提示不存在; 如果表存在,就显示表的结构 比如: desc test01; desc test02; 错误示范: mysql> …

systemserver的inputdispatcher直接产生CANCEL事件原理分析-讨厌的android触摸面试题

背景回顾&#xff1a; 上一个blog已经重点讲解了app层面自己产生的Cancel触摸事件&#xff0c;大概产生的原理如下&#xff1a; 上一个blog地址&#xff1a;https://blog.csdn.net/learnframework/article/details/124086882 即可以看出来&#xff0c;在服务端systemserver其实…

vue3-vant4-vite-pinia-axios-less学习日记

代码地址 GitHub&#xff1a;vue3-vant4-vite-pinia-axios-less 效果如图 1.首页为导航栏 2.绑定英雄页 3.注册页 4.英雄列表页 5.后面不截图了&#xff0c;没啥了 模块 1.vant4&#xff1a;按需引入组件样式文档 2.安装该vite-plugin-vue-setup-extend插件可以直接在…

基于Java+SpringBoot+Vue的图书借还小程序的设计与实现(亮点:多角色、点赞评论、借书还书、在线支付)

图书借还管理小程序 一、前言二、我的优势2.1 自己的网站2.2 自己的小程序&#xff08;小蔡coding&#xff09;2.3 有保障的售后2.4 福利 三、开发环境与技术3.1 MySQL数据库3.2 Vue前端技术3.3 Spring Boot框架3.4 微信小程序 四、功能设计4.1 主要功能描述 五、系统实现5.1 小…

Linux安全加固:保护你的服务器

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

【深度学习实验】线性模型(三):使用Pytorch实现简单线性模型:搭建、构造损失函数、计算损失值

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入库 1. 定义线性模型linear_model 2. 定义损失函数loss_function 3. 定义数据 4. 调用模型 5. 完整代码 一、实验介绍 使用Pytorch实现 线性模型搭建构造损失函数计算损失值 二、…

TensorFlow与pytorch特定版本虚拟环境的安装

TensorFlow与Python的版本对应&#xff0c;注意&#xff0c;一定要选择对应的版本&#xff0c;否则会让你非常痛苦&#xff0c;折腾很久搞不清楚原因。 建议使用国内镜像源安装 没有GPU后缀的就表示是CPU版本的&#xff0c;不加版本就是最新 pip install tensorflow -i https:…

Learn Prompt-人工智能基础

什么是人工智能&#xff1f;很多人能举出很多例子说这就是人工智能&#xff0c;但是让我们给它定义一个概念大家又觉得很难描述的清楚。实际上&#xff0c;人工智能并不是计算机科学领域专属的概念&#xff0c;在其他学科包括神经科学、心理学、哲学等也有人工智能的概念以及相…

Vue3+ElementUI使用

<!DOCTYPE html> <html> <head><meta charset"UTF-8"><meta name"viewport" content"initial-scale1.0,maximum-scale1.0,minimum-scale1.0,user-scalable0, widthdevice-width"/><!-- 引入样式 --><lin…

《C和指针》笔记24: 指针和间接访问

本文主要讲指针和间接访问&#xff0c;标题对应《C和指针对应的章节》&#xff0c;引用的地方是自己写的一些注释、理解和总结。 指针、间接访问和左值 先回顾一下左值和右值 左值代表着一个位置。右值代表着一个值。赋值等号左边是个左值&#xff0c;赋值等号右边是一个右值…