C++11线程库简介

news2024/11/20 3:34:09

前言

        在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();
	//ruhou
	return 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;//原子类型的变量sum
	mutex 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.coloradmin.cn/o/1017619.html

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

相关文章

消息队列(六):服务器设计

紧接着上一章没说完的进行服务器的补充。 推送给消费者消息的基本实现思路 让 brokerServer 把哪些消费者管理好收到对应的消息&#xff0c;把消息推送给消费者 消费者是以队列为维度来订阅消息的&#xff0c;一个队列可以有多个消费者&#xff08;此处我们约定按照轮询的方…

HTTP协议(超级详细)

HTTP协议介绍 基本介绍&#xff1a; HTTP&#xff1a;超文本传输协议&#xff0c;是从万维网服务器传输超文本到本地浏览器的传送协议HTTP是一种应用层协议&#xff0c;是基于TCP/IP通信协议来传送数据的&#xff0c;其中 HTTP1.0、HTTP1.1、HTTP2.0 均为 TCP 实现&#xff0…

激光焊接汽车PP塑料配件透光率测试仪

随着汽车主机厂对车辆轻量化的需求越来越强烈&#xff0c;汽车零部件轻量化设计、制造也成为汽车零部件生产厂商的重要技术指标。零部件企业要实现产品的轻量化&#xff0c;在材料指定的情况下&#xff0c;要通过产品设计优化、产品壁厚减小和装配方式的优化来解决。使用PP材料…

React 把useState变成响应式 ,今天又可以早点下班了

Ⅰ、前言 我们知道 React 中 , 要想修改 「状态」 > 必须要「state &#xff0c; setState」 useState() 中「setState」 去修改 > 「state」那么如果用 Proxy > 去改造 useState&#xff0c;那么 「摸鱼的时间」又增加啦 &#xff1f; Ⅱ、proxy 改造 useState 首…

数据结构与算法之Floyd算法-最短路径问题

Floyd算法-最短路径问题 Floyd算法-最短路径问题算法结束算法思想算法效率分析 Floyd算法-最短路径问题 算法结束 Floyd算法&#xff1a;求出每一对顶点之间的最短路径 核心&#xff1a;使用动态规划思想&#xff0c;将问题的求解分为多个阶段&#xff1a; 对于n个顶点的图…

数据结构---绪论

&#x1f31e;欢迎来到数据结构的世界 &#x1f308;博客主页&#xff1a;卿云阁 &#x1f48c;欢迎关注&#x1f389;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f31f;本文由卿云阁原创&#xff01; &#x1f4c6;首发时间&#xff1a;&#x1f339;2023年9月17日&…

HTTP代理反爬虫技术详解

HTTP代理是一种网络技术&#xff0c;它可以将客户端的请求转发到目标服务器&#xff0c;并将服务器的响应返回给客户端。在网络安全领域中&#xff0c;HTTP代理经常被用来反爬虫&#xff0c;以保护网站的正常运营。 HTTP代理反爬虫的原理是通过限制访问者的IP地址、访问频率、U…

typeScript 类型推论

什么是类型推论&#xff1f; 类型推论是 TypeScript 中的一个特性&#xff0c;它允许开发人员不必显式地指定变量的类型。相反&#xff0c;开发人员可以根据变量的使用情况让 TypeScript 编译器自动推断出类型。例如&#xff0c;如果开发人员将一个字符串赋值给一个变量&#…

【自然语言处理】【大模型】RWKV:基于RNN的LLM

相关博客 【自然语言处理】【大模型】RWKV&#xff1a;基于RNN的LLM 【自然语言处理】【大模型】CodeGen&#xff1a;一个用于多轮程序合成的代码大语言模型 【自然语言处理】【大模型】CodeGeeX&#xff1a;用于代码生成的多语言预训练模型 【自然语言处理】【大模型】LaMDA&a…

MySQL数据库详解 三:索引、事务和存储引擎

文章目录 1. 索引1.1 索引的概念1.2 索引的作用1.3 如何实现索引1.4 索引的缺点1.5 建立索引的原则依据1.6 索引的分类和创建1.6.1 普通索引1.6.2 唯一索引1.6.3 主键索引1.6.4 组合索引1.6.5 全文索引 1.7 查看索引1.8 删除索引 2. 事务2.1 事务的概念2.2 事务的ACID特性2.2.1…

Java 高频疑难问题系列一

​​​​​​​ 目录 ​编辑​​​​​​​ 1.零长度 2.redis的有序集的排序 3.Unsafe类 4.带资源的try语句 5.Spring如何实现计划任务 6.Java中普通代码块,构造代码块,静态代码块执行顺序 7.MyBatis缓存机制 8.Redis Java 2种类型操作转换 9.CAS底层原理和问题 1…

【数据分享】2006-2021年我国城市级别的市容环境卫生相关指标(20多项指标)

《中国城市建设统计年鉴》中细致地统计了我国城市市政公用设施建设与发展情况&#xff0c;在之前的文章中&#xff0c;我们分享过基于2006-2021年《中国城市建设统计年鉴》整理的2006—2021年我国城市级别的市政设施水平相关指标、2006-2021年我国城市级别的各类建设用地面积数…

【pytorch】模型常用函数(conv2d、linear、loss、maxpooling等)

1、二维卷积函数——cnv2d(): in_channels (int): 输入通道数 out_channels (int): 输出通道数 kernel_size (int or tuple): 卷积核大小 stride (int or tuple, optional): 步长 Default: 1 padding (int, tuple or str, optional): 填充 Default: 0 padding_mode (str, optio…

计算机是如何工作的下篇

操作系统&#xff08;Operating System ) 操作系统是一组做计算机资源管理的软件的统称。目前常见的操作系统有&#xff1a;Windows系列、Unix系列、Linux系列、OSX系列、Android系列、iOS系列、鸿蒙等. 操作系统由两个基本功能&#xff1a; 对下,要管理硬件设备. 对上,要给…

数据标注赋能机器学习进行内容审核

数据标注一直以来都是人工智能的基础&#xff0c;是机器学习得以训练的不可或缺的步骤。随着互联网的兴起&#xff0c;如何创建和维护一个健康的网络环境将成为互联网平台不断解决的问题&#xff0c;但对于与日俱增的用户增长和铺天盖地的网络信息&#xff0c;人工审核内容变得…

【牛客网】BC146 添加逗号

一.题目描述 牛客网题目链接:添加逗号_牛客题霸_牛客网 描述: 对于一个较大的整数 N(1<N<2,000,000,000) 比如 980364535&#xff0c;我们常常需要一位一位数这个数字是几位数&#xff0c;但是如果在这 个数字每三位加一个逗号&#xff0c;它会变得更加易于朗读。 因此&a…

指针扩展之——函数指针

前言&#xff1a;小伙伴们好久不见&#xff0c;本篇文章我们继续讲解一个指针的扩展——函数指针。 一.何为函数指针 我们通过对指针的学习已经知道&#xff0c;凡是叫什么什么指针的&#xff0c;都是指指向这个东西的指针。 所以所谓函数指针&#xff0c;也就是指向函数的指…

001 linux 导学

前言 本文建立在您已经安装好linux环境后&#xff0c;本文会向您介绍Shell的一些常用指令 什么是linux Linux是一种自由和开放源代码的类UNIX操作系统&#xff0c;该操作系统的内核由林纳斯托瓦兹在1991年首次发 布&#xff0c;之后&#xff0c;在加上用户空间的应用程序之后…

TypeScript 从入门到进阶之基础篇(一) ts类型篇

系列文章目录 文章目录 系列文章目录前言一、安装必要软件二、TypeScript 基础类型1.基础类型之 数字类型 number2.基础类型之 字符串类型 string3.基础类型之 布尔类型 boolean4.基础类型之 空值类型 void5.基础类型之 null 、undefined类型6.基础类型之 任意类型 any &#x…

Dell戴尔笔记本电脑灵越系列Inspiron 5598原厂Windows10系统2004

戴尔灵越原装出厂系统自带显卡、声卡、蓝牙、网卡等所有驱动、出厂主题壁纸、系统属性戴尔专属LOGO标志、Office办公软件、MyDell等预装程序 链接&#xff1a;https://pan.baidu.com/s/1VYUa7u0-Az4c9bOnWV9GZQ?pwd550m 提取码&#xff1a;550m