目录

简介

本文主要介绍了标准库中的线程部分。线程是目前多核编程里面最重要的一部分。

与进程进程相比,其所需的资源更少,线程之间沟通的方法更多; 他们之间的区别可以比较简明用以下几点概括[1]:


  1. 进程是资源分配的最小单位,线程是CPU调度的最小单位;也就是说进程之间的资源是相互隔离,而线程之间的是可以相互访问的。
  2. 线程的存在依赖于进程,一个进程可以保护多个线程;
  3. 进程出现错误不会影响其他进程,但是一个线程出现错误,会影响同一进程下的所有线程。

线程的使用

线程的创建

一般使用​​std::thread​​​创建一个线程。​​std::thread​​​支持输入一个函数对象,及一些参数,类似于​​std::bind​​,不过没有占位符。

最常见,最简单的是对输入一个匿名函数作为参数:

std::thread t1([]() {
std::cout << "Hello World" << std::endl;
});
t1.join();

需要注意的是,在使用多线程的时候,如果使用类似于​​std::cout << "Hello World" << std::endl;​​的语句,容易造成输出的混乱。比如

std::thread t1([]() {
std::cout << "Hello World1" << std::endl;
});
std::thread t2([]() {
std::cout << "Hello World2" << std::endl;
});
t1.join();
t2.join();

以上代码,我们一般来说期望的输出是

std::thread线程详解(1)_c++

但是,在一些情况下,它还会以以下的方法输出

std::thread线程详解(1)_函数对象_02

造成这个的原因很简单,因为​​"Hello World"​​​和​​std::endl​​​的输出是分开的,所以他们之间可能被插入其他的输出。为了解决这个问题。一般会使用一个完整的字符串进行输出,但是C++在格式化这一方面做的比较差(​​C++20​​​的​​format​​​库看起来还不错),所以一般情况下会使用​​printf​​输出。

线程的方法和属性


  1. ​joinable()​​判断线程是否可连接(可执行线程)的,有以下情况的,为不可连接的:

  1. 构造时,​​thread()​​没有参数;
  2. 该对象的线程已经被移动了;
  3. 该线程已经被​​join​​​或​​detach​​;

  1. ​get_id()​​ 返回线程的ID;
  2. ​native_handle()​​ 返回​​POSIX​​标准的线程对象;
  3. ​join()​​ 等待线程执行完成;
  4. ​detach()​​ 分离线程,分离后对象不再拥有线程。该线程结束后,会自动回收内存。(并不会开启另一个进程);
  5. ​swap()​​ 交换对象的线程。

std::jthread (C++20)

除了常用的​​std::thread​​​外,标准库还存在着另一个可以创建线程的类,​​std::jthread​​​。他们之间的差别比较明显的就是,​​std::jthread​​​会在解构的时候判断线程是否还在运行​​joinable​​​,如果还在运行则自动调用​​request_stop​​​和​​join​​。

除此之外,​​std::jthread​​​还提供了一个内置的​​std::stop_token​​​。可以通过线程函数的第一个参数来获取(如果函数的第一个参数类型为​​std::stop_token​​)。

可以通过​​get_stop_source​​​、​​get_stop_token​​​、​​request_stop​​等方法来对其进行操作。

stop_token (C++20)

​stop_token​​​类似于一个信号,告诉线程是否到了结束的时候。和​​stop_source​​​一起使用。​​stop_token​​​用来获取是否退出(读),而​​stop_source​​用来请求推出(读写)。其方法:


  1. ​request_stop​​ 请求退出
  2. ​stop_requested​​ 获取是否已经请求退出
  3. ​stop_possible​​ 获取是否可以请求退出

样例:

void thread_func(std::stop_token token) {
int data = 0;
while (!token.stop_requested()) {
printf("%d\n", data);
data++;
std::this_thread::sleep_for(1s);
}
printf("Exit\n");
}

int main() {
std::jthread mythread(thread_func);

std::this_thread::sleep_for(4s);

return 0;
}

输出:

std::thread线程详解(1)_c++_03

总结

本次讲述了线程创建的一些方法,可以看到相比较C语言而言,由于C++11提出的函数对象(普通函数、匿名函数,​​std::bind​​的输出等)使得线程的创建更加的方便。

下一次将讲述线程之间的通信。在C++中,线程之间的通信方法和C语言提供的类似,不过是将其包装了一下。