首页 > 编程笔记 > C++笔记 阅读:15

C++ std::jthread线程类用法详解(附带实例)

C++11 的 std::thread 类代表单线程执行且允许多个函数同时执行。然而,它有很大的不便之处:
C++20 提供了改善的线程类 std::jthread(来自可结合线程),当对象被销毁但可结合时将自动调用 join()。更进一步,此类型支持通过 std::stop_source/std::stop_token 来取消并且它的析构函数在线程 join 前也会请求其停止。

在本节中,你将学习如何使用 std::jthread 线程类。为了使用 std::jthread,C++ 程序中需要引入<thread>头文件。对于 std::stop_source和std::stop_token,你需要引入头文件 <stop_token>

C++ std::jthread的使用方式

典型的使用可结合线程和相应取消机制的场景如下:
1) 如果你想使线程对象在作用域外自动 join,则使用 std::jthread 而不是 std::thread。你仍然可使用 std::thread 类里的所有方法,比如调用 join() 来显式 join:
void thread_func(int i)
{
    while(i-- > 0)
    {
        std::cout << i << '\n';
    }
}
int main()
{
    std::jthread t(thread_func, 10);
}

2) 如果你需要能取消线程执行,你应该如下这么做:
>
void thread_func(std::stop_token st, int& i)
{
    while(!st.stop_requested() && i < 100)
   {
       using namespace std::chrono_literals;
        std::this_thread::sleep_for(200ms);
        i++;
    }
}

int main()
{
    int a = 0;
    std::jthread t(thread_func, std::ref(a));

    using namespace std::chrono_literals;
    std::this_thread::sleep_for(1s);

    t.request_stop();

    std::cout << a << '\n';       // prints 4
}

3) 如果你需要取消多个线程的工作,则可如下做:
>
void thread_func(std::stop_token st, int& i)
{
    while(!st.stop_requested() && i < 100)
   {
       using namespace std::chrono_literals;
        std::this_thread::sleep_for(200ms);
        i++;
    }
}

int main()
{
    int a = 0;
    int b = 10;
    std::stop_source st;
    std::jthread t1(thread_func, st.get_token(), std::ref(a));
    std::jthread t2(thread_func, st.get_token(), std::ref(b));

    using namespace std::chrono_literals;
    std::this_thread::sleep_for(1s);

    st.request_stop();

    std::cout << a << ' ' ' << b << '\n';    // prints 4 // and 14
}

4) 如果在 stop_source 的请求取消时,你需要执行部分代码,你可以使用 std::stop_token 对象创建的 std::stop_callback,当停止请求发送后,此回调函数将被调用(通过与 std::stop_token 关联的 std::stop_source 对象):
void thread_func(std::stop_token st, int& i)
{
    while(!st.stop_requested() && i < 100)
   {
       using namespace std::chrono_literals;
        std::this_thread::sleep_for(200ms);
        i++;
    }
}

int main()
{
    int a = 0;
    std::stop_source src;
    std::stop_token token = src.get_token();
    std::stop_callback cb(token,
        []{std::cout << "the end\n";});
    std::jthread t(thread_func, token, std::ref(a));

    using namespace std::chrono_literals;
    std::this_thread::sleep_for(1s);

    src.request_stop();

    std::cout << a << '\n';    // prints "the end" and 4
}

深度剖析C++ std::jthread

std::jthread 跟 std::thread 十分类似。实际上,它尝试修复 C++11 线程中的问题。

std::jthread 的公共接口与 std::thread 也十分类似。所有 std::thread 中的方法在 std::jthread 中也有。

然而,std::jthread在以下几个关键方面有所区别:
你可以像创建 std::thread 一样创建 std::jthread 对象。然而传递给 std::jthread 的可调用函数接收 std::stop_token 类型作为第一个参数。

当你需要取消线程执行时,典型的场景包括图形用户界面里用户交互可能取消正在进行的工作,也可以预见很多其他类似的场景。此类线程函数的调用如下:
函数线程必须周期性地检查 std::stop_token 对象的状态。stop_requested() 方法检查停止请求是否发送。停止请求来自 std::stop_source 对象。

如果多个 stop token 关联同一个 stop source,则停止请求对所有 stop token 可见。如果停止请求了,则它无法被撤回,且连续的停止请求没有任何意义。为了请求停止,你应该调用 request_stop() 方法。通过调用 stop_possible() 方法,你可以检查 std::stop_source 是否与停止状态关联及是否可停止请求。

如果在 stop source 请求停止时,你需要调用回调函数,那么你可以使用 std::stop_callback 类。它将 std::stop_token 对象与回调函数绑定。当 stop token 的 stop source 请求停止时,回调函数被调用。回调函数调用如下:
你可以给同一 stop token 创建任意数量的 std::stop_callback 对象。然而,这些回调函数的调用顺序是不一定的。唯一保证的是在 std::stop_callback 对象创建后,请求停止,它们将会同步执行。

另外重要的一点是,如果任意回调函数返回异常,std::terminate() 会被调用。

相关文章