std::jthread与std::thread的区别是什么?

为什么不是选择往std::thread添加新接口,而是引入了一个新的标准库?
关注者
72
被浏览
53,028

7 个回答

std::jthread修复了std::thread不是RAII类型的设计缺陷,并且增加了能够主动取消或停止线程执行的新特性。选择引入了一个std::jthread,而不是在原有的std::thread上添加新接口,主要原因是为了向后兼容。因为一些应用程序希望使用std::thread的特性(在正在运行的线程的离开作用域直接终止程序),同时std::jthread引入的新功能,也打破库的二进制兼容性。

You might wonder, why we did not just fix std::thread instead of introducing a new type std::jthread. The reason is backward compatibility. There might be a few applications that want to terminate the program when leaving the scope of a running thread. And for some new functionality discussed next we would also break binary compatibility.
---std::jthread作者Nicolai M. Josuttis在其《C++20 - The Complete Guide》一书中的注解

std::thread的缺陷

std::thread要求正在运行的线程在其生命周期结束时调用join()(等待正在运行的线程结束),或调用detach()(让线程在后台运行)。如果这两个函数都没有被调用,析构函数将立即导致程序异常终止并产生core dump。例如如下的代码将在运行时异常终止:

void FuncWithoutJoinOrDetach() {
  std::thread t{task, task_args};
  // 没有调用t.join()或t.detach()
}  // t的生命周期结束时将调用std::terminate(),异常结束程序

即使我们调用join()来等待正在运行的线程结束,仍然可能出现异常安全的问题:

void FuncWithExceptionSafety() {
  // 启动线程执行task
  std::thread t{task, task_args};
  ... // 中间可能会发生异常,在异常上调用std::terminate()
  // 等待task执行结束
  t.join();

所以需要使用try-catch来进行异常处理,确保异常发生后join()函数也能被正常调用:

void FuncWithoutExceptionSafety() {
  // 启动线程执行task
  std::thread t{task, task_args};
  try {
  } catch (...) {
    //  阻塞等待t运行结束
    t.join();
    // 重新抛出异常
    throw
  t.join();

std::jthread修复了std::thread不是RAII类型的设计缺陷

jthread包装了std::thread,在析构函数中调用join()函数(jthread的j是joining的缩写),修复了std::thread不是RAII类型的缺陷:

// destructor:
inline jthread::~jthread() {
  if (joinable()) {
    // if not joined/detached, signal stop and wait for end:
    request_stop();
    join();

所以在c++20之后应该优先考虑使用jthread,如下的代码没有任何问题:

// since c++20
void FuncWithJthread() {
  std::jthread t{task, task_args};
} // t 析构时会调用join()

std::jthread增加了能够主动取消或停止线程执行的新特性

调用线程的join()函数后可能需要等待很长时间,甚至是永远等待。由于线程不像进程允许我们主动发送kill信号终止它,已经启动的线程只能自己结束运行或结束整个程序来结束该线程。因此,std::jthread除了提供std::stop_token能够主动取消或停止正在执行的线程,还增加了std::stop_callback允许在停止线程操作时调用一组回调函数。

例如如下代码将允许当前线程调用request_stop()之后,主动终止线程池中启动的其他多个线程,线程池中的线程将在st.stop_requested处被终止:

{
  std::vector<std::jthread> threads;
  for (int i = 0; i < numThreads; ++i) {
    threads.push_back(std::jthread{[&](std::stop_token st) {
      while (!st.stop_requested()) {
  // request stops before we start to join via destructor:
  for (auto& t : threads) {
    t.request_stop();
}  // destructor stops all threads

另外std::stop_token和std::stop_callback独立于std::thread,还可以用于其他地方,即使在同一个线程里面也可以使用,例如:

int main() {
  // create stop_source and stop_token:
  std::stop_source ssrc;
  std::stop_token stok{ssrc.get_token()};
  // register callback: