C++ `std::jthread`:C++20 自动加入的线程与协作取消

好的,各位观众老爷,欢迎来到今天的“C++线程进阶:jthread,你值得拥有”讲座!今天我们不讲虚的,直接上干货,聊聊C++20里那个让人眼前一亮的std::jthread

开场白:告别手动join的时代

在C++11引入std::thread之后,我们终于可以愉快地创建线程了。但是,用过std::thread的都知道,它有个小麻烦:线程对象析构的时候,要么手动join(等待线程结束),要么detach(让线程独立运行)。一不小心忘了,程序直接崩溃,给你一个惊喜的std::terminate

这种手动管理的方式,让很多程序员,包括我本人,都感觉有点“原始”。毕竟,都2024年了,我们想要的是更智能、更省心的线程管理方案。

所以,std::jthread带着光环来了!它最核心的改进就是:RAII风格的自动join。也就是说,jthread对象析构的时候,会自动等待线程结束,再也不用担心忘记join导致程序崩溃了。

std::jthread的庐山真面目

std::jthread本质上就是一个增强版的std::thread,它在std::thread的基础上增加了以下特性:

  1. 自动join 这是最核心的特性,也是我们使用jthread的主要原因。
  2. 协作式取消: jthread提供了一种优雅的方式来取消线程的执行,避免了粗暴的pthread_cancel之类的方法带来的风险。
  3. stop_token jthread通过std::stop_token来传递取消信号,允许线程检查是否被请求停止。
  4. 构造函数增强: jthread的构造函数支持完美转发,可以更方便地传递参数给线程函数。

代码示例:告别崩溃,拥抱优雅

让我们通过几个代码示例来感受一下jthread的魅力。

示例1:自动join

#include <iostream>
#include <thread>
#include <chrono>

void worker_thread() {
  std::cout << "Worker thread started...n";
  std::this_thread::sleep_for(std::chrono::seconds(2));
  std::cout << "Worker thread finished...n";
}

int main() {
  std::jthread my_thread(worker_thread);
  std::cout << "Main thread continues...n";
  // my_thread 离开作用域,自动 join
  return 0;
}

在这个例子中,我们创建了一个std::jthread对象my_thread,并在其中执行worker_thread函数。当main函数结束时,my_thread对象离开作用域,它的析构函数会自动调用join等待worker_thread函数执行完毕,然后再释放资源。这意味着,即使你忘记手动join,程序也不会崩溃!

示例2:协作式取消

#include <iostream>
#include <thread>
#include <chrono>
#include <stop_token>

void long_running_task(std::stop_token stopToken) {
  for (int i = 0; i < 10; ++i) {
    if (stopToken.stop_requested()) {
      std::cout << "Task cancelled!n";
      return;
    }
    std::cout << "Working... " << i << "n";
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
  }
  std::cout << "Task completed!n";
}

int main() {
  std::jthread my_thread(long_running_task);

  std::this_thread::sleep_for(std::chrono::seconds(1));
  my_thread.request_stop(); // 请求停止线程

  std::cout << "Main thread exiting...n";
  return 0;
}

在这个例子中,我们使用std::stop_token来实现协作式取消。long_running_task函数接收一个std::stop_token对象,并在循环中检查stopToken.stop_requested()是否返回true。如果返回true,则表示线程被请求停止,函数可以优雅地退出。

main函数中,我们调用my_thread.request_stop()来请求停止线程。jthread会将取消信号传递给long_running_task函数,使其能够及时退出。

深入std::stop_token:取消的艺术

std::stop_token是实现协作式取消的关键。它提供以下方法:

  • stop_requested():检查是否被请求停止,返回bool类型。
  • stop_possible():检查是否支持停止,返回bool类型。
  • register_callback(callback):注册一个回调函数,当线程被请求停止时,回调函数会被执行。

表格:std::stop_token方法总结

方法 描述 返回值类型
stop_requested() 检查是否被请求停止 bool
stop_possible() 检查是否支持停止 bool
register_callback(callback) 注册回调函数,当线程被请求停止时执行 void

示例3:使用回调函数

#include <iostream>
#include <thread>
#include <chrono>
#include <stop_token>

void callback_function() {
  std::cout << "Cancellation callback executed!n";
}

void task_with_callback(std::stop_token stopToken) {
  std::stop_callback callback(stopToken, callback_function); //注册回调

  while (!stopToken.stop_requested()) {
    std::cout << "Working...n";
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
  }
  std::cout << "Task stopped.n";
}

int main() {
  std::jthread my_thread(task_with_callback);

  std::this_thread::sleep_for(std::chrono::seconds(1));
  my_thread.request_stop();

  std::cout << "Main thread exiting.n";
  return 0;
}

在这个例子中,我们使用std::stop_callback来注册一个回调函数callback_function,当线程被请求停止时,callback_function会被执行。 注意:std::stop_callback本身也是一个RAII对象,它的析构函数会自动取消回调的注册。

std::jthread的构造函数:完美转发的福音

std::jthread的构造函数支持完美转发,这意味着你可以像使用std::thread一样,将任意数量的参数传递给线程函数。

#include <iostream>
#include <thread>

void print_message(const std::string& message, int count) {
  for (int i = 0; i < count; ++i) {
    std::cout << message << " (" << i + 1 << ")n";
  }
}

int main() {
  std::jthread my_thread(print_message, "Hello from thread!", 3);
  std::cout << "Main thread continues...n";
  return 0;
}

在这个例子中,我们将字符串"Hello from thread!"和整数3作为参数传递给print_message函数。jthread的构造函数会自动将这些参数完美转发给print_message函数,避免了手动使用std::bind或者lambda表达式的麻烦。

std::jthreadstd::thread的对比

为了更好地理解std::jthread的优势,让我们将其与std::thread进行对比。

表格:std::jthreadstd::thread对比

特性 std::thread std::jthread
自动join
协作式取消
stop_token
完美转发
默认析构行为 std::terminate join

从上表可以看出,std::jthread在线程管理和取消方面具有明显的优势。它可以帮助我们编写更安全、更健壮的多线程程序。

std::jthread的适用场景

std::jthread适用于以下场景:

  • 需要自动join的线程: 如果你不想手动管理线程的生命周期,jthread是你的最佳选择。
  • 需要协作式取消的线程: 如果你需要一种优雅的方式来取消线程的执行,jthread可以帮助你实现。
  • 需要传递复杂参数的线程: jthread的完美转发特性可以让你更方便地传递参数给线程函数。

注意事项:detach()的诱惑与陷阱

jthread也提供了detach()方法,允许你将线程与jthread对象分离,使其独立运行。但是,强烈不建议这样做,除非你真的非常清楚自己在做什么。

detach()之后,jthread对象不再管理线程的生命周期,线程会一直运行,直到它自己结束。这意味着,你需要自己负责线程的资源管理,否则可能会导致内存泄漏或者其他问题。

此外,detach()之后,stop_token机制也会失效,你无法再使用request_stop()来取消线程的执行。

总结:jthread,多线程编程的瑞士军刀

std::jthread是C++20引入的一个非常实用的线程管理工具。它通过自动join、协作式取消和完美转发等特性,简化了多线程编程,提高了代码的安全性。

总而言之,std::jthread就像一把瑞士军刀,让你在多线程编程的世界里更加游刃有余。下次写多线程程序的时候,不妨试试它,相信你会爱上它的!

结尾:感谢收看,下课!

感谢各位观众老爷的观看!希望今天的讲座对你有所帮助。记住,多线程编程是一门艺术,需要不断学习和实践。祝大家编程愉快,bug永不相见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注