前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >新的线程:C++20 std::jthread

新的线程:C++20 std::jthread

作者头像
艰默
发布2023-11-05 17:36:09
3410
发布2023-11-05 17:36:09
举报
文章被收录于专栏:iDoitnow

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,其默认行为是中止进程。

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

以上述代码所示,如果没有调用t.join()t.detach(),当线程对象t生命周期结束的时候,可能会产生core dump,导致程序异常终止。上述例子中,在实例化对象t后,即使调用线程tjoin()函数,有时候可能需要等待很长时间才能将线程ttask执行完成,甚至是永久的等待(例如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的错误例子:

代码语言:javascript
复制
#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;
}

可能的输出:

代码语言:javascript
复制
thr.joinable(): true

libc++abi: terminating
[1]    2326 abort      ./test

从输出可以看出程序异常终止了。

下面我们将thread替换为jthread,由于jthread的对象thr在析构的时候,会自动调用自身的join函数,保证主线程要等待thr执行完毕再进行下一步操作。

代码语言:javascript
复制
#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;
}

输出:

代码语言:javascript
复制

thr.joinable(): true

Joinable std::thread

3.2 线程中断

对于线程中断,std::jthread主要引入以下三个停止信号处理:

  • get_stop_source() :返回与线程的停止状态关联的 stop_source 对象。
  • get_stop_token() :返回与线程的共享停止状态关联的 stop_token
  • **request_stop() **:请求执行经由线程的共享停止状态停止。

下面我们通过几个个例子简单了解一下它的具体用法。

示例1:

代码语言:javascript
复制
#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';
    
}

输出:

代码语言:javascript
复制
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:

代码语言:javascript
复制
#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);
}

输出:

代码语言:javascript
复制
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:

代码语言:javascript
复制
#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
}
}

可能的输出:

代码语言:javascript
复制
main: other: 140166751352704
140166751336192
other: 140166751336192
other: 140166751336192
other: 140166751336192
other: 140166751336192
other thread stopped!

4. 总结

jthread基于std::thread主要增加了以下两个功能:

  • jthread 对象被析构时,会自动调用join,等待其所表示的执行流结束。
  • jthread支持外部请求中止(通过 get_stop_sourceget_stop_token request_stop )。

std::jthread 中的自动join和外部请求中止功能使编写更安全的代码变得更加容易,但其性能上相对于thread也增加了开销。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-11-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iDoitnow 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. std::jthread是什么
  • 2. 为什么要引入jthread
  • 3. 如何使用
    • 3.1自动join()
      • 3.2 线程中断
      • 4. 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档