C++11 线程库—线程操作(更新中)

前言

在C++11推出线程库前,Windows和Linux操作系统的线程操作并不同,这就导致多线程程序无法跨平台,如果想要跨平台,会很麻烦并且容易出错。C++11推出的线程库就解决了这一问题。
因为在Windows和Linux操作系统中有一些独特的常量,宏,所以可以凭借这些判断当前运行的平台,然后C++11的线程库通过条件编译,和独特的常量完成了跨平台编码。这就是C++11线程库的重要意义

PS:本篇博客仅记录常用语法

文章目录

  • 前言
  • 线程操作
    • 1. thread类
      • (1). 构造函数—创建线程
      • (2). 析构函数—销毁线程
      • (3). 线程等待—回收资源
      • (4). 赋值重载
      • (6). id类
      • (6). 获取线程ID
      • (7). 线程分离
      • (8). 线程交换
    • 2. this_thread命名空间
      • (1). get_id
      • (2).yield
  • 结束语

C++11 线程库—线程操作(更新中)_第1张图片

C++文档链接:C plus plus
C++11线程库总共有这些操作

C++11 线程库—线程操作(更新中)_第2张图片

线程操作

首先,最简单的,我们先学习创建线程相关的操作。在thread这个头文件中
而在thread头文件中,有一个thread类this_thread命名空间
我们先来学习thread类的相关操作

C++11 线程库—线程操作(更新中)_第3张图片

1. thread类

thread类包装了线程的相关操作,如创建,等待,交换等

(1). 构造函数—创建线程

C++11 线程库—线程操作(更新中)_第4张图片

首先我们来看thread类的构造函数,也就是创建线程。

函数声明 说明
thread( ) noexcept 不会抛异常的无参构造
template explicit thread( Fn&& fn,Args &&… args ) 支持列表初始化和可变参数初始化,不支持隐式类型转换的有参构造
thread(const thread&)=delete 不支持拷贝构造
thread(thread&& x) noexcept 移动构造

PS:
noexcept 在函数声明后作标识符,默认是noexcept(true),表示不会抛异常
class... Args 是可变参数模板
explicit 表示不支持隐式类型转换
=delete 在函数声明后,表示不会生成该函数

(2). 析构函数—销毁线程

析构函数其实就是封装了Linux中pthread_destroy,销毁线程,这样的系统调用接口

(3). 线程等待—回收资源

如果创建线程后,没有等待,直接析构函数销毁的话,会被强制报错,因为要确保线程资源的回收
C++11 线程库—线程操作(更新中)_第5张图片

线程库还提供判断一个线程是否join的函数—joinable

C++11 线程库—线程操作(更新中)_第6张图片
返回真表明该线程没有join等待

用法如下:

thread t1(....);
thread t2(....);

if(t1.joinable()) t1.join();
if(t2.joinable()) t2.join();

接下来,我们结合前三点简单展示线程创建—线程等待—线程销毁的过程

线程的启动函数目的是对全局变量x进行++,加到传参n
lambda表达式的使用可参看C++lambda表达式

代码如下:

#include
#include

using namespace std;

int x = 0;

void test1()
{
	//创建第一个线程
	//启动函数使用lambda表达式
	thread t1([](int n) 
	{
		for (int i = 0; i < n; ++i)
		{
			++x;
		}
	},1000);//1000是传参给lambda表达式的
	
	//创建第二个线程
	thread t2([](int n)
	{
		for (int i = 0; i < n; ++i)
		{
			++x;
		}
	},1000);

	cout << "x = " << x << endl;
	//线程等待
	t1.join();
	t2.join();
	
	//出作用域后,调用析构函数销毁线程
}

int main()
{
	test1();
	return 0;
}

运行结果如下;

在这里插入图片描述

而如果没有join,则会直接报错
C++11 线程库—线程操作(更新中)_第7张图片

(4). 赋值重载

C++11 线程库—线程操作(更新中)_第8张图片

函数声明 说明
thread& operator= (thread&& rhs) noexcept 不会抛异常的移动构造
thread& operator= (const thread&) = delete 不支持拷贝构造

我们用一个程序展示赋值重载的使用

#include
#include
#include
using namespace std;
int x = 0;

void test()
{
	thread threads[5];

	for (int i = 0; i < 5; ++i)
	{
		//使用匿名对象赋值,移动构造
		threads[i] = thread([](int n) 
		{
			for (int j = 0; j < n; j++)
			{
				++x;
			}
		},(i+1)*100);
	}

	for (int i = 0; i < 5; ++i)
	{
		threads[i].join();
	}

	cout << "x = " << x << endl;
}

int main()
{
	test();

	return 0;
}

赋值重载不支持拷贝构造,但支持移动构造,所以不能接收左值,可以接收右值,所以意味着可以使用匿名对象
以上代码就是先实例化一个线程数组,然后使用匿名对象赋值,完成多线程操作。

(6). id类

id类是thread类的一个内部类,其作用是存储线程的一些属性,比如线程ID。同时其重载了各种运算符,作用于线程的比较

C++11 线程库—线程操作(更新中)_第9张图片

同时也有operator<<的重载,方便输出线程ID

在这里插入图片描述

(6). 获取线程ID

C++11 线程库—线程操作(更新中)_第10张图片

get_id返回一个id类,主要是直接cout输出线程ID

thread t1();
cout<<t1.get_id()<<endl;

但是此方法不常用,因为需要通过对象调用,而输出线程ID一般是在启动函数中使用,启动函数中无法通过对象调用,所以输出线程ID的常用方法是this_thread命名空间的get_id。我们稍后讲解

(7). 线程分离

线程分离的函数是detach。线程调用detach后会与创建他的线程分离,即不需要join等待
C++11 线程库—线程操作(更新中)_第11张图片

(8). 线程交换

C++11 线程库—线程操作(更新中)_第12张图片

第一个swap,是通过对象调用的,第二个是静态成员函数,通过类名可直接调用


2. this_thread命名空间

this_thread命名空间,提供了4个成员函数

C++11 线程库—线程操作(更新中)_第13张图片
因为thread类的成员函数无法在启动函数中调用,所以this_thread命名空间解决了这一问题

函数声明 说明
get_id 获取线程ID
yield 让出时间片
sleep_until 使线程休眠到一个时间点
sleep_for 使线程休眠一段时间

(1). get_id

通过this_thread调用get_id,我们就可以在启动函数中,获取当前线程的ID了
C++11 线程库—线程操作(更新中)_第14张图片

需要注意的是,this_thread的get_id也是返回id类
C++11 线程库—线程操作(更新中)_第15张图片

(2).yield

yield是让出当前线程的时间片,需要注意的是,yield同Sleep一样,不会解锁,所以想通过yield实现多线程交替运行,需要在yield的上下解锁和加锁
切出时间片,操作系统会保存上下文,切回来时,直接从yield后开始运行

mutex.unlock();//解锁
this_thread::yield();//让出时间片
mutex.lock();//加锁

sleep_until和sleep_for的使用较为麻烦,后续补充

结束语

感谢你的阅读

如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述

你可能感兴趣的:(C++学习笔记,c++,开发语言,linux)