目录
简介
本文主要介绍了标准库中的线程部分。线程是目前多核编程里面最重要的一部分。
与进程进程相比,其所需的资源更少,线程之间沟通的方法更多; 他们之间的区别可以比较简明用以下几点概括[1]:
- 进程是资源分配的最小单位,线程是CPU调度的最小单位;也就是说进程之间的资源是相互隔离,而线程之间的是可以相互访问的。
- 线程的存在依赖于进程,一个进程可以保护多个线程;
- 进程出现错误不会影响其他进程,但是一个线程出现错误,会影响同一进程下的所有线程。
线程的使用
线程的创建
一般使用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();
以上代码,我们一般来说期望的输出是
但是,在一些情况下,它还会以以下的方法输出
造成这个的原因很简单,因为"Hello World"
和std::endl
的输出是分开的,所以他们之间可能被插入其他的输出。为了解决这个问题。一般会使用一个完整的字符串进行输出,但是C++在格式化这一方面做的比较差(C++20
的format
库看起来还不错),所以一般情况下会使用printf
输出。
线程的方法和属性
-
joinable()
判断线程是否可连接(可执行线程)的,有以下情况的,为不可连接的:
- 构造时,
thread()
没有参数; - 该对象的线程已经被移动了;
- 该线程已经被
join
或detach
;
-
get_id()
返回线程的ID; -
native_handle()
返回POSIX
标准的线程对象; -
join()
等待线程执行完成; -
detach()
分离线程,分离后对象不再拥有线程。该线程结束后,会自动回收内存。(并不会开启另一个进程); -
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
用来请求推出(读写)。其方法:
-
request_stop
请求退出 -
stop_requested
获取是否已经请求退出 -
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;
}
输出:
总结
本次讲述了线程创建的一些方法,可以看到相比较C语言而言,由于C++11提出的函数对象(普通函数、匿名函数,std::bind
的输出等)使得线程的创建更加的方便。
下一次将讲述线程之间的通信。在C++中,线程之间的通信方法和C语言提供的类似,不过是将其包装了一下。