C++11--线程库的认识

news2024/10/6 1:36:54

目录

thread

线程的构造方式

相关成员函数

join与detach

线程传参

 互斥量mutex

mutex

Locks

 原子性操作库

条件变量


thread

线程的构造方式

 它是不支持拷贝构造,赋值的,但是可以支持移动构造,移动赋值。还可以直接创建无参的对象。

它的有参的构造是支持可变参数模板的,

fn:可调用对象,比如函数指针、仿函数、lambda表达式、被包装器包装后的可调用对象等。args...:调用可调用对象fn时所需要的若干参数。

例如:

使用构造函数:

void Print1(int n)
{
    for (int i = 0; i < n; i++)
    {
        cout << "hello" << endl;
    }
}

int main()
{
    int n = 1000;
    thread t1(Print1, n);
    thread t2(Print1, n );

    t1.join();
    t2.join();
    return 0;
}

使用lambda:

thread t3([&]() {
		for (int i = 0; i < n; i++)
		{
			cout << "hello" << endl;
		}
	  }
	);
t3.join();

也可以使用移动构造:

   //thread t3 = t1;//不允许
	thread t4 = thread(Print1, n);//移动构造
	t4.join();

相关成员函数

 例如:常用的函数

join:对该线程进行等待,在等待的线程返回之前,调用join函数的线程将会被阻塞

detach:将该线程与创建线程进行分离,被分离后的线程不再需要创建线程调用join函数对其进行等待。

get_id:获取该线程的id

joinable:判断该线程是否已经执行完毕,如果是则返回true,否则返回false.

joinable函数还可以用于判定线程是否是有效的这些情况,线程都是无效的

采用无参构造函数构造的线程对象。(该线程对象没有关联任何线程)
线程对象的状态已经转移给其他线程对象。(已经将线程交给其他线程对象管理)
线程已经调用join或detach结束。(线程已经结束)

但上面这些函数都是需要通过线程对象来获取的,如果要在线程对象关联的线程函数中获取线程id,可以调用this_thread命名空间下的get_id函数。

该空间下的函数:

 yield:只让当前线程退出运行状态,让其它线程去调度。

join与detach

当一个线程退出时,需要对该线程所使用的资源进行回收,否则可能会导致内存泄露等问题。

可以是主线程调用join函数,对线程进行回收,或者也可以调用detach函数将新线程与主线程进行分离,分离后新线程会在后台运行,其所有权和控制权将会交给C++运行库,此时C++运行库会保证当线程退出时,其相关资源能会被正确回收。

这里也存在一个问题,若在某些情况下,导致线程不能被正确回收,例如:

void Print1(int n,int& k)
{
	for (int i = 0; i < n; i++)
	{
		k++;
	}
}
void Errno(bool& flag)
{
	flag = true;
}
int main()
{
	int n = 100000;
	int k = 0;
	thread t1(Print1,n,ref(k));
	thread t2(Print1,n,ref(k));
	bool flag = false;

	Errno(flag);
	if (flag)
		return -1;
	t1.join();
	t2.join();
	cout << "k=" << k << endl;
	return 0;
}

这里可以借用RAII思想解决,代码示例:


class RAII
{
public:
	RAII(thread& t)
		:t_(t)
	{
	}
	~RAII()
	{
		if (t_.joinable())
		{
			t_.join();
			cout << "线程回收" << endl;
		}
	}
private:
	thread& t_;
};
int main()
{
	int n = 100000;
	int k = 0;
	thread t1(Print1,n,ref(k));
	thread t2(Print1,n,ref(k));
	RAII T1(t1);
	RAII T2(t2);
	bool flag = false;

	Errno(flag);
	if (flag)
		return -1;
	t1.join();
	t2.join();
	cout << "k=" << k << endl;
	return 0;
}

结果:

线程传参

线程传参时是以值拷贝的方式拷贝到线程空间当中的,即使将参数设置为引用,也不会改变外边的变量。

例如:

void Print1(int n,int& m)
{
	for (int i = 0; i < n; i++)
	{
		cout << "hello" << endl;
		m++;
	}
}

int main()
{
	int n = 1000;
	int m = 0;
	thread t1(Print1, n,m);
	thread t2(Print1, n,m);
	cout << m << endl;

	t1.join();
	t2.join();
	return 0;
}

这段代码在vs2019还下不能运行

 解决方法:

使用std::ref函数,当线程中函数的参数为引用时,可以借助ref函数保持对实参的引用,而不是线程栈空间中的拷贝。

例如:

thread t1(Print1, n,ref(m));
thread t2(Print1, n,ref(m));

将函数的参数改为指针类型,例如:

 或者使用lambda,例如:

 互斥量mutex

mutex

以这段代码为例:

void Print1(int n,int& k)
{
	for (int i = 0; i < n; i++)
	{
		k++;
	}
}

int main()
{
	mutex mu_;
	int n = 100000;
	int k = 0;
	thread t1(Print1,n,ref(k));
	thread t2(Print1,n,ref(k));

	t1.join();
	t2.join();
	cout << "k=" << k << endl;
	return 0;
}

由于k++不是原子性的操作,当多个执行流去对这个变量操作时,可能会出现二义性。

结果:

对于临界资源,要进行加锁保护。C++11提供了4种锁:

 

 都是不能进行拷贝的。不同的锁分别用于不同的场景中。最常使用的就是mutex。

该锁的成员函数有:

 try_lock:尝试对该线程加锁,若成功会返回TRUE,失败返回false。

对上面的代码进行加锁:

Locks

在对代码进行加锁的过程中,可能因为加锁的代码太长,进行了误操作。或者抛异常等原因,从而导致死锁问题。这里便也可以使用RAII思想。在需要加锁的地方,创建一个Lock对象,其构造函数会进行加锁,在其生命结束时,自动调用析构函数进行解锁。

例如:

template<class Lock>
class LockGuard
{
public:
	LockGuard(Lock& lk)
		:_lock(lk)
	{
		_lock.lock();
		cout << "thread:" << this_thread::get_id() << "加锁" << endl;
	}

	~LockGuard()
	{
		cout << "thread:" << this_thread::get_id() << "解锁" << endl << endl;
		_lock.unlock();
	}
private:
	Lock& _lock;
};

c++11也提供了两个类型用来加锁,其中unique_lock还提供了一些成员函数,使用上更加灵活。

 例如:

 原子性操作库<atomic>

当一个数据被多个执行流访问时,除了加锁进行互斥访问外,C++11中引入了原子操作类型。

 

 支持模板,可以定任意原子类型数据

对上面代码进行更改:

补充:定义为原子数据的变量,是不能进行赋值,拷贝构造,移动构造的。

 

条件变量

 使用是要搭陪锁(互斥量)去使用,以第一个类为例。它们提供的成员函数可分为两大类。

 一个是等待,一个是去唤醒。

等待:以wait为例

 提供了两个版本:

一个是直接传入unique<mutex_>,若线程调用wait后会立即被阻塞,直到被唤醒。

另一个还需传入一个返回值为bool的对象,当线程被唤醒后还需要调用传入的可调用对象,如果可调用对象的返回值为false,那么该线程还会被继续被阻塞。

唤醒:

 一个是唤醒等待队列中的第一个线程,另一个是唤醒全部线程

代码测试:以两个线程交替打印100位例,一个线程打印奇数,另一个打印偶数;


int main()
{
	int i = 0;
	int n = 100;
	mutex mtx;
	thread t1([&](){
		while (i < n)
		{
			while (i % 2 == 0)
			{
				this_thread::yield();
			}
			cout << "t1--" << this_thread::get_id() << ":" << i << endl;
			i += 1;
		}
	});

	// t2打印偶数
	thread t2([&]() {
		while (i < n)
		{
			while (i % 2 != 0)
			{
				this_thread::yield();
			}
			cout <<"t2--"<<this_thread::get_id() << ":" << i << endl;
			i += 1;
		}
	});

	this_thread::sleep_for(chrono::seconds(3));

	cout << "t1:" << t1.get_id() << endl;
	cout << "t2:" << t2.get_id() << endl;

	t1.join();
	t2.join();

	return 0;
}

 使用条件变量:

int main()
{
	int i = 0;
	int n = 100;
	mutex mtx;
	condition_variable cv;
	bool ready = true;

	
	thread t1([&](){//打印奇数
		while (i < n)
		{
			unique_lock<mutex> lock(mtx);
			cv.wait(lock, [&ready]() {return !ready; });

			cout << "t1--" << this_thread::get_id() << ":" << i << endl;
			i += 1;
			ready = true;
			cv.notify_one();
			
			//this_thread::yield();
			//this_thread::sleep_for(chrono::microseconds(100));
		}
	});

	thread t2([&]() {// t2打印偶数
		while (i < n)
		{
			unique_lock<mutex> lock(mtx);
			cv.wait(lock, [&ready](){return ready; });

			cout <<"t2:"<<this_thread::get_id() << ":" << i << endl;
			i += 1;
			ready = false;
			cv.notify_one();
		}
	});

	this_thread::sleep_for(chrono::seconds(3));
	cout << "t1:" << t1.get_id() << endl;
	cout << "t2:" << t2.get_id() << endl;

	t1.join();
	t2.join();

	return 0;
}

结果:

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

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

相关文章

存储器(一)

目录 一、存储器的分类 1.按介质分类 1.1半导体存储器 1.2磁表面存储器 1.3光盘存储器 2.按存取方式分类 2.1随机存储器(RAM) 2.2只读存储器(ROM) 2.3串行访问存储器 3.按在计算机中的作用分类 ​编辑 二、存储器的层次结构 1.存储器的主要性能指标: 2.存储系统体系…

模式识别是什么意思

模式识别是一种通过分析数据特征、模型、算法等手段&#xff0c;从数据中寻找规律、发现隐藏的模式或结构的技术。通常是从某些对象、场景、过程等方面入手&#xff0c;对数据进行处理&#xff0c;以便于对这些对象、场景、过程进行分类、检测、识别、分割、分析等目的。 模式…

HJ20 密码验证合格程序

写在前面&#xff1a; 题目链接&#xff1a;牛客网 华为机试题 HJ20 密码验证合格程序 题目难度&#xff1a;中等 编程语言&#xff1a;C 一、题目描述 描述 密码要求: 1.长度超过8位 2.包括大小写字母.数字.其它符号,以上四种至少三种 &#xff08;注&#xff1a;其他符号不…

死信队列

死信队列 死信的概念 先从概念解释上搞清楚这个定义&#xff0c;死信&#xff0c;顾名思义就是无法被消费的消息&#xff0c;字面意思可以这样理解&#xff0c;一般来说&#xff0c;producer 将消息投递到 broker 或者直接到queue 里了&#xff0c;consumer 从 queue 取出消息…

[Pandas] 构建DataFrame数据框

DataFrame是二维数据结构&#xff0c;数据以行和列的形式排列 构建DataFrame最基本的定义格式如下 df pd.DataFrame(dataNone, indexNone, columnsNone) 参数说明 data: 具体数据 index: 行索引&#xff0c;如果没有指定&#xff0c;会自动生成RangeIndex(0,1,2,...,n) colu…

mongoose使用详细 -- 如何通过mongoose搭建服务器

前言 授人以鱼不如授人以渔&#xff0c;这篇文章详细介绍了&#xff0c;对于一个从来没有听说过mongoose的小菜鸟如何快速了解和上手mongoose 其他一些开源库可以借助类似的方法进行学习 提前需要准备的工具 1.官网文档 Mongoose :: Documentation 官网提供了很多例子讲解&am…

[Pandas] 查看DataFrame的常用属性

导入数据 import pandas as pddf pd.DataFrame([[L123,A,0,123],[L456,A,1,456],[L437,C,0,789],[L112,B,1,741],[L211,A,0,852],[L985,B,1,963]],columns[Material,Level,Passing,LT]) df 1.dtypes: 查看DataFrame中各列的数据类型 df.dtypes会返回每个字段的数据类型及Da…

C++练级之初级:第六篇

类和对象入门级&#xff1a;第六篇 1.类的引入2.类的定义2.1类的访问限定符2.2类的封装2.3类的实例化 3.如何计算类或者对象的大小4.this指针 总结 我们知道&#xff0c;C在C语言的基础上引入了对象的概念&#xff0c;那么从本篇开始进入类和对象&#xff1b; 1.类的引入 &…

【JavaEE】_1.多线程(1)

目录 1.操作系统 2. 进程 3. CPU分配——进程调度 3.1 操作系统对进程的管理 3.2 PCB的属性 3.2.1 基础属性 3.2.2 实现进程调度的属性 4. 内存分配——内存管理 4.1 虚拟地址空间 4.2 进程间通信 5. 线程 5.1 线程的概念 5.2 创建与使用多线程 5.2.1 方式1&a…

【数据结构】八大排序(一)

&#x1f61b;作者&#xff1a;日出等日落 &#x1f4d8; 专栏&#xff1a;数据结构 珍惜自己的时间&#xff0c;利用好每一份每一秒。做事不放过没一个细节&#xff0c;小心谨慎&#xff0c;细致&#xff0c;能够做到这些&#xff0c;还有什么是不可能的呢? 目录 ​编辑 ✔…

【刷题之路Ⅱ】LeetCode 61. 旋转链表

【刷题之路Ⅱ】LeetCode 61. 旋转链表 一、题目描述二、解题1、方法1——移动部分链表1.1、思路分析1.2、代码实现 2、方法1——闭合为环2.1、思路分析2.2、代码实现 一、题目描述 原题连接&#xff1a; 61. 旋转链表 题目描述&#xff1a; 给你一个链表的头节点 head &#x…

【Python | matplotlib】matplotlib.cm的理解以及举例说明

文章目录 一、模块介绍二、颜色举例 一、模块介绍 matplotlib.cm是Matplotlib中的一个模块&#xff0c;它提供了一组用于处理颜色映射&#xff08;colormap&#xff09;的函数和类。颜色映射是一种将数值映射到颜色的方法&#xff0c;常用于制作热力图、等值线图、散点图等。 …

软件工程实验:原型设计

目录 前言实验目的实验要求实验过程系统原型绘制生成html代码 总结 前言 本次实验的主题是原型设计&#xff0c;即根据用户需求和系统功能&#xff0c;设计一个简单的软件原型&#xff0c;展示系统的界面和交互方式。原型设计是软件工程中的一种重要技术&#xff0c;它可以帮助…

深入探索PyTorch中的自动微分原理及梯度计算方法

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

如何完全卸载linux下通过rpm安装的mysql

卸载linux下通过rpm安装的mysql 1.关闭MySQL服务2.使用 rpm 命令的方式查看已安装的mysql3. 使用rpm -ev 命令移除安装4. 查询是否还存在遗漏文件5. 删除MySQL数据库内容 1.关闭MySQL服务 如果之前安装过并已经启动&#xff0c;则需要卸载前请先关闭MySQL服务 systemctl stop…

Tomcat整体架构解析

一、Tomcat整体架构介绍 Tomcat是一个开源的轻量级web应用服务器。整体架构如下&#xff1a; Tomcat中最顶层的容器是Server&#xff0c;即代表一个Tomcat服务器&#xff0c;一个Server中可以有多个Service&#xff0c;对外提供不同的web服务。Service是对Connector和Contain…

电话号码的字母组合

题目&#xff1a;17. 电话号码的字母组合 - 力扣&#xff08;Leetcode&#xff09; 思路&#xff1a; 给定一个电话号码字符串 digits&#xff0c;须输出它所能表示的所有字母组合。我们可以先定义一个数字字符到字母表的映射表 numToStr&#xff0c;然后再用 Combine 函数递归…

【Linux专区】 环境搭建 | 带你白嫖七个月阿里云服务器

&#x1f49e;&#x1f49e;欢迎来到 Claffic 的博客&#x1f49e;&#x1f49e; &#x1f449; 专栏&#xff1a;《Linux专区》&#x1f448; 前言&#xff1a; 工欲善其事必先利其器&#xff0c;没个Linux环境怎么愉快地学Linux&#xff1f;这期就先带大家把环境搞好&#xf…

物联网系统中常见的通信协议分析

物联网&#xff08;Internet of Things, 简称IoT&#xff09;是指将各种传感器、设备等通过互联网连接起来&#xff0c;形成一个庞大的网络&#xff0c;实现物与物之间的互联互通。在实现这个过程中&#xff0c;各种不同的通信协议被广泛应用。本文将为大家介绍物联网中常见的通…

[架构之路-185]-《软考-系统分析师》-3-操作系统基本原理 - 文件索引表

目录 一、文件的索引块。 二、索引分配表 三、索引表的链接方案 四、多层索引 五、混合索引分配 一、文件的索引块。 存放在目录中的文件&#xff0c;并非是文件的真实内容。 目录中记录了文件的索引块是几号磁盘块。 文件对应的索引表是存放在指定的磁盘块中的&#x…