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++文档链接: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的使用较为麻烦,后续补充

结束语

感谢你的阅读

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

本文链接:https://my.lmcjl.com/post/11951.html

展开阅读全文

4 评论

留下您的评论.