C++20 新特性:std::jthread线程新纪元

std::stop_token 和 std::jthread

一直以来,我们在使用 std::thread 时,需要手动管理线程的 join()detach(),并且不能直接支持外部请求的中止,这带来了额外的性能开销和编程复杂性。根据 C++ 长期以来的零开销设计哲学,C++20 引入了 std::jthreadstd::stop_token。这些新特性不仅自动处理线程的加入,还支持协作式的线程取消,极大简化了线程的使用和管理,而不增加任何未使用功能的成本。 让我们深入了解一下它们的使用和设计哲学。

std::jthread 和 std::stop_token

std::jthread

std::jthread 是 C++20 引入的一个新的线程类,它与 std::thread 类似,但提供了一些重要的改进:

  1. 自动管理生命周期std::jthread 在作用域结束时会自动调用 join,因此不需要显式地调用 joindetach
  2. 内置停止机制std::jthreadstd::stop_token 集成,支持直接请求停止线程。
  • 成员类型
    • id: std::thread::id
    • native_handle_type (可选): std::thread::native_handle_type
  • 成员函数
    • 构造函数: 构造新的jthread对象
    • 析构函数: 如果线程是可加入的,请求停止并加入线程
    • operator=: 移动jthread对象
    • joinable: 检查线程是否可加入,即可能在并行上下文中运行
    • get_id: 返回线程的id
    • native_handle: 返回底层的实现定义线程句柄
    • hardware_concurrency [静态]: 返回实现支持的并发线程数
  • 操作
    • join: 等待线程完成执行
    • detach: 允许线程独立于线程句柄执行
    • swap: 交换两个jthread对象
  • 停止令牌处理
    • get_stop_source: 返回与线程的共享停止状态关联的stop_source对象
    • get_stop_token: 返回与线程的共享停止状态关联的stop_token
    • request_stop: 通过共享停止状态请求执行停止

我们可以看到 std::jthread支持所有std::thread的功能并提供了扩展。在自动处理线程生命周期的同时,仍提供joindetach方法,保证使用的灵活性和控制。这样设计既维持了与std::thread的接口一致性,方便开发者使用和过渡。

std::stop_token

下是一个关于 std::stop_token 及其相关类的功能和用途的表格:

类名 说明
std::stop_source 用于发起停止请求的对象。它负责生成 std::stop_token,并在需要时发出停止信号。
std::stop_token std::stop_source 创建的令牌,用于在线程中检测是否有停止请求。可以传递给线程,使其能定期检查并决定是否停止执行。
std::stop_callback 允许注册回调函数,这些函数在 std::stop_source 发出停止请求时被调用,提供一种响应停止请求的机制。

共同工作机制

  1. 创建并发起取消请求std::stop_source 对象允许发出取消信号。它可以独立于任何线程存在,并在创建时不自动发起取消请求。
  2. 生成停止令牌std::stop_source 对象可以生成 std::stop_token,这些令牌可以被多个接收者共享和检查,以确定是否已经请求停止执行。
  3. 传递停止令牌 :将 std::stop_token 传递给需要支持取消操作的线程或任务。在 C++20 中,std::jthread 自动管理线程的生命周期,并接收一个 std::stop_token 作为其运行函数的参数,这简化了令牌的传递和管理。
  4. 定期检查停止请求 :在长时间运行或循环任务中,应定期调用 std::stop_token::stop_requested() 来检查是否有停止请求。如果返回 true,则表示应当尽快安全地停止执行。
  5. 响应取消请求 :线程或任务应根据 std::stop_token 的检查结果决定是否退出。这通常涉及清理资源、保存状态等安全退出的步骤。

示例代码

c++ 复制代码
#include <iostream>
#include <chrono>
#include <thread>
#include <stop_token>

// 使用 std::jthread 运行的函数
void task(std::stop_token stoken) {
    // 4. 定期检查停止请求
    while (!stoken.stop_requested()) {
        std::cout << "任务正在运行..." << std::endl;
        // 模拟一些工作
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    // 5. 响应取消请求
    std::cout << "任务已收到停止请求,现在停止运行。" << std::endl;
}

int main() {
    // 1. 创建并发起取消请求的源
    std::stop_source stopSource;
    
    // 2. 生成停止令牌
    std::stop_token stoken = stopSource.get_token();
    
    // 3. 传递停止令牌
    std::jthread worker(task, stoken);

    // 模拟主线程运行一段时间后需要停止子线程
    std::this_thread::sleep_for(std::chrono::seconds(5));
    std::cout << "主线程请求停止子线程..." << std::endl;
    
    // 触发停止请求
    stopSource.request_stop();

    // std::jthread 在析构时自动加入
    return 0;
}

底层实现机制

std::jthread 在底层实现中利用了 std::thread 的功能,但添加了对线程取消的支持。std::jthread 的主要特点是它在析构时会自动执行线程的加入(join),并且支持协作式取消。这意味着,如果线程函数接受 std::stop_token 作为参数,std::jthread 会自动从其内部的 std::stop_source 获取一个 std::stop_token 并传递给线程函数,从而使得线程可以在适当的时候响应取消请求。 std::stop_sourcestd::stop_token 提供了一种管理和查询停止状态的机制。std::stop_source 负责发起停止请求,而 std::stop_token 允许在关联的 std::stop_source 发起停止请求后进行查询。当 std::stop_source 调用 request_stop() 时,它会修改内部状态以表示停止已被请求,并通过与之关联的所有 std::stop_token 反映这一状态变更。 这种设计使得 std::jthread 和其协作的取消机制不仅简化了线程的使用,还增强了线程间的协作和通信能力,提高了代码的安全性和可维护性。

std::stop_tokenstd::stop_callback的其他使用案例;

另外, std::stop_tokenstd::stop_callback 并不局限于与线程(如 std::jthread)的使用,它们独立于线程的,用于程序中的任何地方,以提供一种灵活的停止信号处理机制。

c++ 复制代码
#include <iostream>
#include <chrono>
#include <stop_token>

int main() {
    std::stop_source source;
    std::stop_token token = source.get_token();

    // 模拟一些可以被取消的工作
    auto startTime = std::chrono::steady_clock::now();
    auto endTime = startTime + std::chrono::seconds(10);  // 设定10秒后结束任务

    while (std::chrono::steady_clock::now() < endTime) {
        if (token.stop_requested()) {
            std::cout << "Task was canceled!" << std::endl;
            break;
        }
        std::cout << "Working..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));

        // 模拟在某个条件下请求停止
        if (std::chrono::steady_clock::now() > startTime + std::chrono::seconds(5)) {
            source.request_stop();
        }
    }

    if (!token.stop_requested()) {
        std::cout << "Task completed normally." << std::endl;
    }

    return 0;
}

在这个示例中,我们在主线程中运行一个循环,并通过 std::stop_source 来控制何时应该停止循环。这种方式非常适合于不需要多线程但需要响应取消请求的场景。

它们也可以与 std::thread 结合使用,但使用方式略有不同,因为 std::thread 没有内建的支持来直接接受 std::stop_token。下面是如何在 std::thread 中使用

c++ 复制代码
#include <iostream>
#include <thread>
#include <stop_token>

void threadFunction(std::stop_token stopToken) {
    std::stop_callback cb(stopToken, [](){
        std::cout << "Thread stopping as requested." << std::endl;
    });

    while (!stopToken.stop_requested()) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "Thread is running..." << std::endl;
    }
}

int main() {
    std::jthread t(threadFunction);

    std::this_thread::sleep_for(std::chrono::seconds(5));
    t.request_stop();  // Request to stop the thread.

    t.join();
    return 0;
}

在这个示例中,std::jthread 在构造时自动接收一个 std::stop_token,该令牌被传递到 threadFunction。在 threadFunction 中,我们注册了一个 std::stop_callback,当外部请求停止线程时,这个回调会被触发,输出一条消息并检查 stop_requested() 来优雅地停止线程。

如果你确实需要在 std::thread 中使用 std::stop_token,你可能需要手动管理这种机制,比如通过线程之间共享的 std::atomic<bool> 或通过其他同步手段来传递停止信号。但通常来说,使用 std::jthread 更为方便和安全。

总结

总的来说,std::jthread 解决 std::thread 缺乏自动资源管理(RAII)的问题,它自动管理线程的生命周期,避免了资源泄漏和死锁的风险,并通过 std::stop_token增加了能够主动取消或停止线程执行的新特性。

相关推荐
小镇程序员2 小时前
vue2 src自定义事件
前端·javascript·vue.js
AlgorithmAce4 小时前
Live2D嵌入前端页面
前端
nameofworld4 小时前
前端面试笔试(六)
前端·javascript·面试·学习方法·递归回溯
前端fighter4 小时前
js基本数据新增的Symbol到底是啥呢?
前端·javascript·面试
GISer_Jing5 小时前
从0开始分享一个React项目:React-ant-admin
前端·react.js·前端框架
川石教育5 小时前
Vue前端开发子组件向父组件传参
前端·vue.js·前端开发·vue前端开发·vue组件传参
GISer_Jing6 小时前
Vue前端进阶面试题目(二)
前端·vue.js·面试
乐闻x6 小时前
Pinia 实战教程:构建高效的 Vue 3 状态管理系统
前端·javascript·vue.js
weixin_431449686 小时前
web组态软件
前端·物联网·低代码·编辑器·组态
橘子味小白菜6 小时前
el-table的树形结构后端返回的id没有唯一键怎么办
前端·vue.js