前言
在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++文档链接:C plus plus
C++11线程库总共有这些操作
线程操作
首先,最简单的,我们先学习创建线程相关的操作。在thread
这个头文件中
而在thread头文件中,有一个thread类
和this_thread命名空间
我们先来学习thread类的相关操作
1. thread类
thread类包装了线程的相关操作,如创建,等待,交换等
(1). 构造函数—创建线程
首先我们来看thread类的构造函数,也就是创建线程。
函数声明 | 说明 |
---|---|
thread( ) noexcept | 不会抛异常的无参构造 |
template<class Fn , class… Args> 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). 线程等待—回收资源
如果创建线程后,没有等待,直接析构函数销毁的话,会被强制报错,因为要确保线程资源的回收
线程库还提供判断一个线程是否join的函数—joinable
返回真表明该线程没有join等待
用法如下:
thread t1(....);
thread t2(....);
if(t1.joinable()) t1.join();
if(t2.joinable()) t2.join();
接下来,我们结合前三点简单展示线程创建—线程等待—线程销毁
的过程
线程的启动函数目的是对全局变量x进行++,加到传参n
lambda表达式的使用可参看C++lambda表达式
代码如下:
#include<iostream>
#include<thread>
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,则会直接报错
(4). 赋值重载
函数声明 | 说明 |
---|---|
thread& operator= (thread&& rhs) noexcept | 不会抛异常的移动构造 |
thread& operator= (const thread&) = delete | 不支持拷贝构造 |
我们用一个程序展示赋值重载的使用
#include<iostream>
#include<windows.h>
#include<thread>
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。同时其重载了各种运算符,作用于线程的比较
同时也有operator<<的重载,方便输出线程ID
(6). 获取线程ID
get_id返回一个id类,主要是直接cout输出线程ID
thread t1();
cout<<t1.get_id()<<endl;
但是此方法不常用,因为需要通过对象调用,而输出线程ID一般是在启动函数中使用,启动函数中无法通过对象调用,所以输出线程ID的常用方法是this_thread命名空间的get_id
。我们稍后讲解
(7). 线程分离
线程分离的函数是detach。线程调用detach后会与创建他的线程分离,即不需要join等待
(8). 线程交换
第一个swap,是通过对象调用的,第二个是静态成员函数,通过类名可直接调用
2. this_thread命名空间
this_thread命名空间,提供了4个成员函数
因为thread类的成员函数无法在启动函数中调用,所以this_thread命名空间解决了这一问题
函数声明 | 说明 |
---|---|
get_id | 获取线程ID |
yield | 让出时间片 |
sleep_until | 使线程休眠到一个时间点 |
sleep_for | 使线程休眠一段时间 |
(1). get_id
通过this_thread调用get_id,我们就可以在启动函数中,获取当前线程的ID了
需要注意的是,this_thread的get_id也是返回id类
(2).yield
yield是让出当前线程的时间片,需要注意的是,yield同Sleep一样,不会解锁,所以想通过yield实现多线程交替运行,需要在yield的上下解锁和加锁
切出时间片,操作系统会保存上下文,切回来时,直接从yield后开始运行
mutex.unlock();//解锁
this_thread::yield();//让出时间片
mutex.lock();//加锁
sleep_until和sleep_for的使用较为麻烦,后续补充
结束语
感谢你的阅读
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。