好的,没问题,直接进入主题!
大家好!今天我们要聊聊C++20中闪亮登场的std::jthread
。这玩意儿可不是std::thread
的简单升级版,而是一个更智能、更安全的多线程利器。它自带“自动join
”和“协作取消”两大绝技,能让我们的多线程代码优雅又高效。
1. std::thread
的那些糟心事儿
在std::jthread
横空出世之前,我们用std::thread
创建线程,日子过得并不总是那么舒坦。比如,忘记join
或detach
线程,轻则导致程序异常退出,重则造成内存泄漏,简直是噩梦。
先看一个简单的例子:
#include <iostream>
#include <thread>
void do_something() {
std::cout << "Thread is doing something...n";
// 模拟耗时操作
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Thread finished.n";
}
int main() {
std::thread t(do_something);
// Oops! 忘记join或detach了!
return 0;
}
这段代码编译可以通过,但是运行的时候,如果 t
在 main
函数结束前还没有执行完,程序就会直接 terminate
,因为 t
析构的时候会检查是否 joinable
,如果 joinable
且没有 join
或 detach
,就会调用 std::terminate
。
我们需要手动处理线程的生命周期,确保线程在程序结束前要么执行完毕(join
),要么和主线程分离(detach
)。但是人总有疏忽的时候,一旦忘记处理,就会埋下隐患。
2. std::jthread
:自动join
,妈妈再也不用担心我忘记join
了!
std::jthread
就像一个贴心的管家,它会在自身析构时自动join
其管理的线程。这意味着,只要你创建了一个std::jthread
对象,就再也不用担心忘记join
导致程序崩溃了。
#include <iostream>
#include <thread>
#include <jthread> // 注意:需要包含这个头文件
void do_something() {
std::cout << "Thread is doing something...n";
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Thread finished.n";
}
int main() {
std::jthread t(do_something);
// 即使没有显式调用join,程序也不会崩溃,因为jthread会自动join
return 0;
}
在这个例子中,我们把std::thread
换成了std::jthread
,其他代码保持不变。现在,即使我们忘记了join
,程序也能正常退出,因为std::jthread
的析构函数会自动调用join
。
3. std::jthread
的构造函数
std::jthread
的构造函数和std::thread
类似,可以接受一个可调用对象作为参数。
- 默认构造函数: 创建一个没有关联线程的
std::jthread
对象。 - 带可调用对象的构造函数: 创建一个关联了线程的
std::jthread
对象。 - 移动构造函数: 将一个
std::jthread
对象的所有权转移到另一个std::jthread
对象。 - 拷贝构造函数:
std::jthread
禁止拷贝构造,避免资源管理混乱。
#include <iostream>
#include <jthread>
void my_function(int arg) {
std::cout << "Thread executing with argument: " << arg << std::endl;
}
int main() {
// 使用可调用对象构造jthread
std::jthread t1(my_function, 42);
// 使用 lambda 表达式构造 jthread
std::jthread t2([](const std::stop_token& stoken) {
while (!stoken.stop_requested()) {
std::cout << "Thread running...n";
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
std::cout << "Thread stopped.n";
});
// 移动构造
std::jthread t3 = std::move(t1); // t1不再拥有线程,t3拥有
// t1 现在是空的,不再管理任何线程
if (t1.joinable()) {
std::cout << "t1 is joinablen"; // 这段代码不会执行
} else {
std::cout << "t1 is not joinablen"; // 这段代码会执行
}
std::this_thread::sleep_for(std::chrono::seconds(3));
t2.request_stop(); // 请求 t2 停止
return 0;
}
4. 协作取消:优雅地停止线程
std::jthread
的另一个杀手锏是“协作取消”。它允许你向线程发送取消请求,线程可以选择在合适的时机响应这个请求,从而实现优雅的停止。
std::jthread
向线程函数传递一个std::stop_token
对象。线程函数可以通过stop_token.stop_requested()
方法检查是否收到了取消请求。如果收到了请求,线程可以选择停止执行并退出。
#include <iostream>
#include <jthread>
void do_something(std::stop_token stoken) {
while (!stoken.stop_requested()) {
std::cout << "Thread is working...n";
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Thread is stopping...n";
}
int main() {
std::jthread t(do_something);
// 模拟主线程执行一段时间后,请求取消线程
std::this_thread::sleep_for(std::chrono::seconds(3));
t.request_stop(); // 发送取消请求
return 0;
}
在这个例子中,线程函数do_something
会不断循环,直到stop_token.stop_requested()
返回true
。在main
函数中,我们等待3秒后,调用t.request_stop()
发送取消请求。线程函数收到请求后,会停止循环并退出。
5. std::stop_token
和std::stop_source
std::stop_token
是只读的,线程函数只能通过它来检查是否收到了取消请求。如果你需要在多个线程之间共享取消信号,可以使用std::stop_source
。
std::stop_source
是取消信号的来源,它可以创建std::stop_token
对象,并向所有持有相同std::stop_token
对象的线程发送取消请求。
#include <iostream>
#include <jthread>
#include <vector>
void do_something(std::stop_token stoken, int id) {
while (!stoken.stop_requested()) {
std::cout << "Thread " << id << " is working...n";
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Thread " << id << " is stopping...n";
}
int main() {
std::stop_source stop_source;
std::vector<std::jthread> threads;
// 创建多个线程,并传递相同的stop_token
for (int i = 0; i < 3; ++i) {
threads.emplace_back(do_something, stop_source.get_token(), i);
}
// 模拟主线程执行一段时间后,请求取消所有线程
std::this_thread::sleep_for(std::chrono::seconds(5));
stop_source.request_stop(); // 发送取消请求给所有线程
return 0;
}
在这个例子中,我们创建了一个std::stop_source
对象,并使用stop_source.get_token()
方法获取std::stop_token
对象,传递给多个线程。当调用stop_source.request_stop()
时,所有持有相同std::stop_token
对象的线程都会收到取消请求。
6. std::stop_callback
:取消时的回调
有时候,我们希望在线程收到取消请求时执行一些清理工作。std::stop_callback
可以让我们注册一个回调函数,在线程收到取消请求时自动执行。
#include <iostream>
#include <jthread>
void do_something(std::stop_token stoken) {
// 在收到取消请求时执行的回调函数
std::stop_callback callback(stoken, []() {
std::cout << "Cancellation callback executed.n";
// 在这里执行清理工作
});
while (!stoken.stop_requested()) {
std::cout << "Thread is working...n";
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Thread is stopping...n";
}
int main() {
std::jthread t(do_something);
// 模拟主线程执行一段时间后,请求取消线程
std::this_thread::sleep_for(std::chrono::seconds(3));
t.request_stop(); // 发送取消请求
return 0;
}
在这个例子中,我们使用std::stop_callback
注册了一个回调函数,当线程收到取消请求时,回调函数会被自动执行。
7. jthread
vs thread
:对比表格
特性 | std::thread |
std::jthread |
---|---|---|
自动join |
不支持 | 支持 |
协作取消 | 不支持 | 支持 |
异常安全性 | 较低 | 较高 |
默认行为 | 需要手动管理 | 自动管理 |
是否可拷贝 | 禁止 | 禁止 |
是否可移动 | 支持 | 支持 |
8. 何时使用std::jthread
?
- 需要自动管理线程生命周期时:
std::jthread
可以避免忘记join
或detach
导致的错误。 - 需要优雅地停止线程时:
std::jthread
的协作取消机制可以让你在线程收到取消请求时执行一些清理工作。 - 希望代码更安全、更易维护时:
std::jthread
的自动管理和协作取消机制可以减少代码中的错误,提高代码的可读性和可维护性。
9. 注意事项
- 如果线程函数抛出异常,
std::jthread
会自动捕获异常并重新抛出,这可以避免程序崩溃。 - 如果线程函数长时间阻塞,
std::jthread
的自动join
可能会导致主线程阻塞。在这种情况下,可以考虑使用std::future
或其他异步机制。 request_stop()
只是发送一个请求,线程是否真正停止取决于线程函数的实现。 线程函数必须检查stop_token
并做出响应。
10. 总结
std::jthread
是C++20中一个非常有用的多线程工具。它通过自动join
和协作取消机制,简化了多线程编程,提高了代码的安全性、可读性和可维护性。在编写多线程代码时,应该优先考虑使用std::jthread
。
希望今天的讲解对大家有所帮助!记住,std::jthread
是你的多线程好帮手,有了它,再也不用担心线程管理的那些糟心事儿啦!下次再见!