网站首页 > 基础教程 正文
jthread是c++20所支持的新的线程类型,jthread = joinable thread, 即可以自动join的线程。我们知道在c++11之后,c++标准库开始支持多线程编程,那么thread和jthread之间有何区别,本文将进行重点讲解。
c++11 thread
c++11中thread对象如果在销毁之前处于可join的状态,却没有join的话,将会引发一个异常, 例如下面的例子:
#include <iostream>
#include <thread>
int main(){
std::cout << std::endl;
std::cout << std::boolalpha;
std::thread thr{[]{ std::cout << "Joinable std::thread" << std::endl; }};
std::cout << "thr.joinable(): " << thr.joinable() << std::endl;
std::cout << std::endl;
}
在effective modern c++的37条曾经提到过,使std::thread在所有路径最后都不可结合。而这样的需求就很容易让人想起RAII。
class ThreadRAII {
public:
enum class DtorAction { join, detach }; //enum class的信息见条款10
ThreadRAII(std::thread&& t, DtorAction a) //析构函数中对t实行a动作
: action(a), t(std::move(t)) {}
~ThreadRAII()
{ //可结合性测试见下
if (t.joinable()) {
if (action == DtorAction::join) {
t.join();
} else {
t.detach();
}
}
}
std::thread& get() { return t; } //见下
private:
DtorAction action;
std::thread t;
};
这样看起来似乎万事大吉,但是也是有弊端的。effective modern c++ Item39表明了使用ThreadRAII来保证在std::thread的析构时执行join有时不仅可能导致程序表现异常,还可能导致程序挂起,例如下面的例子,thread析构时,程序陷入死锁状态。
#include <iostream>
#include <thread>
#include <future>
std::promise<void> p;
class ThreadRAII {
public:
enum class DtorAction { join, detach }; //enum class的信息见条款10
ThreadRAII(std::thread&& t, DtorAction a) //析构函数中对t实行a动作
: action(a), t(std::move(t)) {}
~ThreadRAII()
{
std::cout << "call ~ThreadRAII" << std::endl;
//可结合性测试见下
if (t.joinable()) {
if (action == DtorAction::join) {
t.join();
} else {
t.detach();
}
}
}
std::thread& get() { return t; } //见下
private:
DtorAction action;
std::thread t;
};
void react()
{
}
void detect()
{
ThreadRAII tr(
std::thread([]
{
p.get_future().wait();
react();
}),
ThreadRAII::DtorAction::join
);
throw 1;//这里抛出了异常
p.set_value();
}
int main()
{
try{
detect();
}
catch(...){
}
}
解决方案是此类程序应该和异步执行的lambda通信,告诉它不需要执行了,可以直接返回,但是C++11中不支持可中断线程(interruptible threads)。
结合上述的例子,jthread的由来就很好理解了,jthread除了拥有std::thread 的行为外,主要增加了以下两个功能:
- jthread 对象被析构时,会自动调用join,等待其所表示的执行流结束。
- jthread支持外部请求中止(通过 get_stop_source、get_stop_token 和 request_stop )。
这两个特点和上面的例子是相呼应的。
jthread
了解了jthread产生的原因,下面将介绍jthread的使用方法。
回顾上节的第一个案例,joinable的std::thread如果析构的时候没有join,将会导致异常退出。
#include <iostream>
#include <thread>
int main(){
std::cout << std::endl;
std::cout << std::boolalpha;
std::thread thr{[]{ std::cout << "Joinable std::thread" << std::endl; }};
std::cout << "thr.joinable(): " << thr.joinable() << std::endl;
std::cout << std::endl;
}
将其改成jthread,再次运行,不再会异常退出。
#include <iostream>
#include <thread>
int main(){
std::cout << std::endl;
std::cout << std::boolalpha;
std::jthread thr{[]{ std::cout << "Joinable std::thread" << std::endl; }};
std::cout << "thr.joinable(): " << thr.joinable() << std::endl;
std::cout << std::endl;
}
下面的这个例子是使用request_stop向线程发出停止运行的请求。注意lambda函数的入参中需要添加std::stop_token类型的入参, 并且需要在线程中添加检查stop_token的代码。
#include <thread>
#include <iostream>
using namespace std::chrono_literals;
void test_jthread01() {
std::jthread jt{ [](std::stop_token stoken) {
while (!stoken.stop_requested()) {
std::cout << "Doing work\n";
std::this_thread::sleep_for(1s);
}
}};
sleep(5);
jt.request_stop(); // 请求线程停止,因有响应停止请求而终止线程
jt.join();
}
int main()
{
test_jthread01();
}
上面我们提到过,使用ThreadRAII来保证在std::thread的析构时执行join有时不仅可能导致程序表现异常,还可能导致程序挂起。
jthread同样可以解决上述问题,jthread对象在析构时会调用request_stop,jthread的析构函数伪代码可能是下面这样的:
~jthread() {
if(joinable()) {
request_stop(); //More on stop request below.
join();
}
}
而在线程的内部可以使用std::condition_variable_any去进行配合。condition_variable_any和一样条件变量不同的是, 其wait时会接受一个stop_token,当收到request_stop时,wait会直接返回,不再进行等待。下面是condition_variable_any进行wait时的伪代码:
template<class Lock, class Predicate>
bool wait(Lock& lock, std::stop_token stoken, Predicate stop_waiting){
while (!stoken.stop_requested()) {
if (stop_waiting()) return true;
wait(lock);
}
return stop_waiting();
}
结合jthread和condition_variable_any就可以很好的解决上述问题,官网对于jthread和condition_variable_any结合的描述如下所示:
If the request_stop() does issue a stop request (i.e., returns true), then all condition variables of base type std::condition_variable_any registered with an interruptible wait for std::stop_tokens associated with the jthread's internal stop-state will be awoken.
意思是说如果发出了request_stop,那么condition_variable_any类型的条件变量将会唤醒。下面是一个使用jthread和condition_variable_any的完整例子,jthread对象析构时,不再会陷入无穷的等待中。
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>
using namespace std::chrono_literals;
std::condition_variable_any cond;
int main()
{
std::jthread waiting_worker([](std::stop_token stoken) {
std::mutex mutex;
std::unique_lock lock(mutex);
std::cout << "wait" << std::endl;
cond.wait(lock, stoken,
[] { return false; });
if (stoken.stop_requested()) {
std::cout << "Waiting worker is requested to stop\n";
return;
}
});
std::this_thread::sleep_for(100s);
std::cout << "destroy jthread object, and call request_stop" << std::endl;
// Or automatically using RAII:
// waiting_worker's destructor will call request_stop()
// and join the thread automatically.
}
总结
jthread扩充了std::thread 的功能,主要增加了以下两个功能:
- jthread 对象被析构时,会自动调用join,等待其所表示的执行流结束。
- jthread支持外部请求中止(通过 get_stop_source、get_stop_token 和 request_stop )。
猜你喜欢
- 2024-11-11 Linux下的C++ socket编程实例 linux c++ tcp
- 2024-11-11 C++11原子变量:线程安全、无锁操作的实例解析
- 2024-11-11 C++11的thread_local原理和应用范例
- 2024-11-11 知识重构-c++ : Lambda 知识重构拼音
- 2024-11-11 c++ 疑难杂症(4) std:vector c++ vector subscript out of range
- 2024-11-11 深入探索C++异步编程的奥秘 c++11异步编程
- 2024-11-11 C++ 开发中使用协程需要注意的问题
- 2024-11-11 golang极速嵌入式Linux应用开发(四)-协程与并发
- 2024-11-11 在计算机编程中,线程是指一个程序内部的执行流程
- 2024-11-11 C++ std:decay、std:bind、std:packaged_task 在模版编程的实践
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)