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.mzph.cn/news/80927.shtml

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

相关文章

Java计算机毕业设计 基于SpringBoot+Vue的毕业生信息招聘平台的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

网络请求【小程序】

一、get 二、post 1.获取相应数据 Page({/*** 页面的初始数据*/data: { inptValue:, isArr:[]},/*** 生命周期函数--监听页面加载*/onLoad(options) {},onSubmit(){// console.log(this.data.inptValue)//2.后台请求数据wx.request({url: https://tea.qingnian8.com/demoArt/…

CentOS7安装MySQL

文章目录 前言一、MySQL5.71.1 安装wget1.2 下载&安装MySQL的rpm源1.3 修改MySQL安装版本1.4 下载并启动MySQL1.5 开启MySQL远程连接用户 二、MySQL8.0注意事项 前言 CentOS7的安装&#xff0c;采用的是yum的方式安装。 yum方式安装&#xff0c;就类似在Windows下不停的下…

flink时间处理语义

背景 在flink中有两种不同的时间处理语义&#xff0c;一种是基于算子处理时间的时间&#xff0c;也就是以flink的算子所在的机器的本地时间为准&#xff0c;一种是事件发生的实际时间&#xff0c;它只与事件发生时的时间有关&#xff0c;而与flink算子的所在的本地机器的本地时…

机器学习——决策树/随机森林

0、前言&#xff1a; 决策树可以做分类也可以做回归&#xff0c;决策树容易过拟合决策树算法的基本原理是依据信息学熵的概念设计的&#xff08;Logistic回归和贝叶斯是基于概率论&#xff09;&#xff0c;熵最早起源于物理学&#xff0c;在信息学当中表示不确定性的度量&…

WorkPlus | 好用、专业、安全的局域网即时通讯及协同办公平台

自国家于2022年发布的《关于加强数字政府建设的指导意见》以来&#xff0c;我国数字政府建设已经迈入了一个全新的里程碑&#xff0c;迎来了全面改革和深化升级的全新阶段。 WorkPlus作为自主可控、可信安全、专属定制的数字化平台&#xff0c;扮演着政务机关、政府单位以及各…

JDK19特性

文章目录 JAVA19概述1. 记录模式(预览版本)2.Linux/RISC-V 移植3.外部函数和内存 API &#xff08;预览版&#xff09;4.虚拟线程(预览版)5.Vector API &#xff08;第四次孵化&#xff09;6.Switch 模式匹配&#xff08;第三预览版&#xff09;7.结构化并发&#xff08;孵化阶…

【算法专题突破】滑动窗口 - 串联所有单词的子串(15)

目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 1. 题目解析 题目链接&#xff1a;30. 串联所有单词的子串 - 力扣&#xff08;LeetCode&#xff09; 这道题其实也很好理解&#xff0c;看一下示例就基本知道是什么意思了&#xff0c; 主要就是找 s 里面…

基于springboot+vue的药店管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

HTML+CSS画一个卡通中秋月饼

HTMLCSS画一个卡通中秋月饼&#x1f96e;&#x1f96e;&#x1f96e; 中秋活动水个文章 整个divcss实现个月饼&#xff0c;给前端初学者一个练手的demo 效果图 思路 HTMl 先来个轮廓画脸上的东西&#xff1a;眼睛、眉毛、腮红、嘴巴眼睛丰富下瞳孔画20个花瓣 CSS 轮廓是要外…

css中BFC外边距塌陷解决办法

什么是BFC 块级格式化上下文&#xff0c;独立的渲染区域&#xff0c;与外部毫不相干&#xff0c;上下两个元素都设置了外边距&#xff0c;结果会出现重叠的部分合并 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /&…

[EI复现】基于主从博弈的新型城镇配电系统产消者竞价策略(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【网络】计算机网络基础

Linux网络 对网络的理解 在网络传输中存在的问题&#xff1a; 找到我们所需要传输的主机解决远距离数据传输丢失的问题怎么进行数据转发&#xff0c;路径选择的问题 有问题&#xff0c;就有解决方案&#xff1b; 我们把相同性质的问题放在一起&#xff0c;做出解决方案 解…

【系统架构】什么是集群?为什么要使用集群架构?

什么是集群&#xff1f;为什么要使用集群架构&#xff1f; 1.什么是集群&#xff1f;2.为什么要使用集群&#xff1f;2.1 高性能2.2 价格有效性2.3 可伸缩性2.4 高可用性2.5 透明性2.6 可管理性2.7 可编程性 3.集群的常见分类3.1 负载均衡集群3.2 高可用性集群3.3 高性能计算集…

LeetCode 2596. 检查骑士巡视方案

【LetMeFly】2596.检查骑士巡视方案 力扣题目链接&#xff1a;https://leetcode.cn/problems/check-knight-tour-configuration/ 骑士在一张 n x n 的棋盘上巡视。在有效的巡视方案中&#xff0c;骑士会从棋盘的 左上角 出发&#xff0c;并且访问棋盘上的每个格子 恰好一次 。…

输入学生成绩,函数返回最大元素的数组下标,求最高分学生成绩(输入负数表示输入结束)

scanfscore()函数用于输入学生的成绩 int scanfscore(int score[N])//输入学生的成绩 {int i -1;do {i;printf("输入学生成绩:");scanf("%d", &score[i]);} while (score[i] > 0);return i; } findmax()用于寻找最大值 int findmax(int score[N…

c语言练习59:深入理解char类型的取值范围

深入理解char类型的取值范围 例如&#xff1a; #include <stdio.h> int main() {char a[1000];int i;for(i0; i<1000; i){a[i] -1-i;}printf("%d",strlen(a));return 0; }结果为255 ab以%d的形式打印结果为&#xff1a;300 而c由于unsigned char的取值范…

DC系列靶机5通关教程

信息收集 主机扫描 sudo arp-scan -l端口扫描 nmap -p- -A 192.168.16.172漏洞发现 浏览器访问靶机IP 在Contact找到类似提交数据的地方 点击submit&#xff0c;数字发生变化。不断刷新的话&#xff0c;数字依然会发生变化 使用bp抓包发送重发器查看数据包 再次点击发送查看…

k8s优雅停服

在应用程序的整个生命周期中&#xff0c;正在运行的 pod 会由于多种原因而终止。在某些情况下&#xff0c;Kubernetes 会因用户输入&#xff08;例如更新或删除 Deployment 时&#xff09;而终止 pod。在其他情况下&#xff0c;Kubernetes 需要释放给定节点上的资源时会终止 po…

浅谈C++|类的成员

一.类对象作为类成员 类可以作为另一个类的成员 代码&#xff1a; #include <iostream> using namespace std; class phone { public:string shouji;phone(string shouji1) :shouji(shouji1) {cout << "phone的构造函数调用" << endl;}~phone() …