新的线程:C++20 std::jthread
1. std::jthread是什么
类jthread
表示单个执行线程。它拥有通常同 std::thread
的行为,除了jthread
在析构时自动再结合,而且能在具体情况下取消/停止。
2. 为什么要引入jthread
std::jthread
在std::thread
基础上,增加了能够主动取消或停止线程执行的新特性。与 std::thread
相比,std::jthread
具有异常安全的线程终止流程,并且在大多数情况下可以替换它,只需很少或无需更改代码。在我们进入细节之前,先说一说std::thread
的缺陷:
std::jthread
使用的时候需要通过join()
来完成等待线程结束,继续join()
后语句的执行,或者调用detach()
来让线程与当前线程分离,移至后台继续运行。
A std::thread instance can be in either the joinable or unjoinable state. A std::thread that is default constructed, detached, or moved is unjoinable. We must join a joinable std::thread explicitly before the end of its life; otherwise, the std::thread’s destructor calls std::terminate, whose default behavior is to abort the process.
std::thread 实例可以处于可联接或不可联接状态。默认构造、分离或移动的 std::thread 不可联接。我们必须在可连接的 std::thread 生命周期结束之前显式加入它;否则,std::thread 的析构函数将调用 std::terminate,其默认行为是中止进程。
void FuncWithoutJoinOrDetach() {
std::thread t{task, task_args};
// 没有调用t.join()或t.detach()
} // t的生命周期结束时将调用std::terminate(),异常结束程序
以上述代码所示,如果没有调用t.join()
或t.detach()
,当线程对象t
生命周期结束的时候,可能会产生core dump,导致程序异常终止。
上述例子中,在实例化对象t
后,即使调用线程t
的join()
函数,有时候可能需要等待很长时间才能将线程t
的task
执行完成,甚至是永久的等待(例如task
中存在死循环),由于thread
不像进程一样允许我们主动将其kill掉,所以当t
中出现死循环,会导致无法继续执行jion()
之后的语句,已经启动的线程只能自己结束运行或结束整个程序来结束该线程。
基于以上两个主要原因,在C++20中引入std::jthread
类,来弥补std::tread
的缺陷,其除了拥有std::thread
的行为外主要新增了以下两个功能:
-
std::jthread
对象被析构时,会自动调用join
,等待其所表示的执行流结束。 -
std::jthread
支持外部请求中止。
3. 如何使用
std::jthred
的基础使用方法与std::thread
的用法一样,这里我们不再赘述,下面我们通过几个例子重点介它新增的两个功能。
3.1自动join()
我们先看一个std::thread
的错误例子:
#include <iostream>
#include <thread>
int main() {
std::cout << '\n';
std::cout << std::boolalpha;
std::thread thr{[]{ std::cout << "Joinable std::thread" << '\n'; }};
std::cout << "thr.joinable(): " << thr.joinable() << '\n';
std::cout << '\n';
return 0;
}
可能的输出:
thr.joinable(): true
libc++abi: terminating
[1] 2326 abort ./test
从输出可以看出程序异常终止了。
下面我们将thread
替换为jthread
,由于jthread
的对象thr
在析构的时候,会自动调用自身的join
函数,保证主线程要等待thr
执行完毕再进行下一步操作。
#include <iostream>
#include <thread>
int main()
{
std::cout << '\n';
std::cout << std::boolalpha;
std::jthread thr{[]
{ std::cout << "Joinable std::thread" << '\n'; }};
std::cout << "thr.joinable(): " << thr.joinable() << '\n';
std::cout << '\n';
return 0;
}
输出:
thr.joinable(): true
Joinable std::thread
3.2 线程中断
对于线程中断,std::jthread
主要引入以下三个停止信号处理:
- get_stop_source() :返回与线程的停止状态关联的
stop_source
对象。 - get_stop_token() :返回与线程的共享停止状态关联的
stop_token
。 - **request_stop() **:请求执行经由线程的共享停止状态停止。
下面我们通过几个个例子简单了解一下它的具体用法。
示例1:
#include <chrono>
#include <iostream>
#include <thread>
using namespace::std::literals;
int main() {
std::cout << '\n';
std::jthread nonInterruptable([]{ // 步骤(1)
int counter{0};
while (counter < 10){
std::this_thread::sleep_for(0.2s);
std::cerr << "nonInterruptable: " << counter << '\n';
++counter;
}
});
std::jthread interruptable([](std::stop_token stoken){ // 步骤(2)
int counter{0};
while (counter < 10){
std::this_thread::sleep_for(0.2s);
if (stoken.stop_requested()) return; // 步骤(3)
std::cerr << "interruptable: " << counter << '\n';
++counter;
}
});
std::this_thread::sleep_for(1s);
std::cerr << '\n';
std::cerr << "Main thread interrupts both jthreads" << '\n';
nonInterruptable.request_stop(); // 步骤(4)
interruptable.request_stop(); // 步骤(5)
std::cout << '\n';
}
输出:
nonInterruptable: interruptable: 00
nonInterruptable: 1
interruptable: 1
nonInterruptable: 2
interruptable: 2
nonInterruptable: 3
interruptable: 3
Main thread interrupts both jthreads
nonInterruptable: 4
nonInterruptable: 5
nonInterruptable: 6
nonInterruptable: 7
nonInterruptable: 8
nonInterruptable: 9
在示例1中,我们启动两个线程:nonInterruptable
(步骤1)和nterruptable
(步骤2)。与 nonInterruptable
不同的是 interruptable
获取一个 std::stop_token
并在步骤3中使用它来检查它是否被中断(即stoken.stop_requested()
)。如果发生停止请求,lambda 函数将返回,interruptable
线程将结束。主程序在步骤5的地方调用interruptable.request_stop();
,触发停止请求,此时interruptable
线程停止,不再进行打印。虽然在步骤4调用了nonInterruptable.request_stop();
,但在nonInterruptable
里未进行对该请求的处理,因此nonInterruptable
继续执行,直到主程序结束。
示例2:
#include <chrono>
#include <iostream>
#include <thread>
using namespace std::chrono_literals;
int main() {
std::cout << std::boolalpha;
auto print = [](std::string_view name, const std::stop_source &source) {
std::cout << name << ": stop_possible = " << source.stop_possible();
std::cout << ", stop_requested = " << source.stop_requested() << '\n';
};
// A worker thread
auto worker = std::jthread([](std::stop_token stoken) {
for (int i = 10; i; --i) {
std::this_thread::sleep_for(300ms);
if (stoken.stop_requested()) {
std::cout << " Sleepy worker is requested to stop\n";
return;
}
std::cout << " Sleepy worker goes back to sleep\n";
}
});
std::stop_source stop_source = worker.get_stop_source();
print("stop_source", stop_source);
std::cout << "\nPass source to other thread:\n";
auto stopper = std::thread(
[](std::stop_source source) {
std::this_thread::sleep_for(500ms);
std::cout << "Request stop for worker via source\n";
source.request_stop();
},
stop_source);
stopper.join();
std::this_thread::sleep_for(200ms);
std::cout << '\n';
print("stop_source", stop_source);
}
输出:
stop_source: stop_possible = true, stop_requested = false
Pass source to other thread:
Sleepy worker goes back to sleep
Request stop for worker via source
Sleepy worker is requested to stop
stop_source: stop_possible = true, stop_requested = true
示例3:
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
int main() {
auto f = [](const stop_token &st) { // jthread负责传入stop_token
while (!st.stop_requested()) { // jthread并不会强制停止线程,需要我们依据stop_token的状态来进行取消/停止操作
cout << "other: " << this_thread::get_id() << "\n";
this_thread::sleep_for(1s);
}
cout << "other thread stopped!\n";
};
jthread jth(f);
cout << "main: " << this_thread::get_id() << "\n";
this_thread::sleep_for(5s);
jth.request_stop(); // 请求停止线程,对应的stop_token的stop_requested()函数返回true(注意,除了手动调用外,jthread销毁时也会自动调用该函数)
// 我们无需在jthread上调用join(),它在销毁时自动join
}
}
可能的输出:
main: other: 140166751352704
140166751336192
other: 140166751336192
other: 140166751336192
other: 140166751336192
other: 140166751336192
other thread stopped!
4. 总结
jthread
基于std::thread
主要增加了以下两个功能:
jthread
对象被析构时,会自动调用join
,等待其所表示的执行流结束。jthread
支持外部请求中止(通过get_stop_source
、get_stop_token
和request_stop
)。
std::jthread
中的自动join
和外部请求中止功能使编写更安全的代码变得更加容易,但其性能上相对于thread
也增加了开销。
文章首发公众号:iDoitnow如果喜欢话,可以关注一下