当前位置: 首页 > article >正文

C++20 中线程管理与取消机制的深度剖析

文章目录

    • `std::jthread`:更智能的线程管理
      • 背景与优势
      • 构造函数与 `std::stop_token` 的集成
    • `std::stop_token`、`std::stop_source` 和 `std::stop_callback`:灵活的取消机制
      • `std::stop_token`:取消请求的指示器
      • `std::stop_source`:取消请求的发起者
      • `std::stop_callback`:取消时的自定义处理
    • 使用示例
      • 简单的线程取消示例
      • 多任务的协作取消示例
    • 结论

在 C++ 编程领域,多线程开发一直是一项具有挑战性的任务,尤其是在确保线程安全和有效管理线程生命周期方面。随着 C++20 的发布,线程管理和取消机制迎来了重大革新,其中 std::jthreadstd::stop_tokenstd::stop_sourcestd::stop_callback 成为了这一改进的核心要素。这些新特性不仅极大地提升了线程管理的安全性和便捷性,还显著增强了线程协作取消的效率与可靠性,为开发者构建高性能、稳健的多线程应用程序提供了强有力的支持。本文将深入探讨这些新特性的底层原理、详细用法以及在实际开发场景中的具体应用。

std::jthread:更智能的线程管理

背景与优势

在传统的 C++ 多线程编程中,std::thread 作为线程管理的基础工具,要求开发者手动调用 join 方法来等待线程执行完毕,以避免资源泄露。然而,手动管理线程的生命周期容易出错,一旦忘记调用 join,就可能导致线程资源无法正确释放,进而引发各种难以调试的问题。

std::jthread 正是为了解决这些痛点而引入的。它作为 std::thread 的增强版本,最大的亮点在于其自动加入(join)机制。当 std::jthread 对象离开其作用域时,它会自动调用 join 方法,确保线程资源得到妥善管理,无需开发者手动干预,从而大大降低了因线程生命周期管理不当而导致的错误风险。

构造函数与 std::stop_token 的集成

std::jthread 的构造函数设计十分灵活,它允许开发者传递一个函数及其相关参数。特别值得注意的是,如果传递的函数接受 std::stop_token 作为其首参数,那么在新线程开始执行时,会自动将该令牌传递给函数。这种设计为线程提供了一种便捷的机制,使其能够在执行过程中随时检查是否收到了取消请求,从而实现线程的可中断执行。

例如,假设有一个复杂的计算任务函数 computeTask,它可能需要花费较长时间来完成。我们可以将其定义为接受 std::stop_token 作为参数:

void computeTask(std::stop_token stopToken) {// 复杂的计算逻辑while (true) {// 执行计算步骤if (stopToken.stop_requested()) {// 处理取消请求break;}}
}

然后,通过 std::jthread 来启动这个任务:

std::jthread taskThread(computeTask, std::stop_token());

这样,在任务执行过程中,如果有其他部分的代码发出了取消请求,computeTask 函数能够及时检测到并做出相应的处理,确保线程能够安全、有序地退出。

std::stop_tokenstd::stop_sourcestd::stop_callback:灵活的取消机制

std::stop_token:取消请求的指示器

std::stop_token 本质上是一个枚举类型,它扮演着取消请求指示器的角色。线程可以通过查询 std::stop_token 的状态,来判断是否收到了取消请求。当与 std::stop_source 配合使用时,std::stop_token 的作用更加显著。

std::stop_source:取消请求的发起者

std::stop_source 是一个专门用于发出取消请求的对象。它就像是一个控制中心,一旦调用其 request_stop 方法,所有与之关联的 std::stop_token 都会接收到取消通知。这种一对多的关联机制,使得在多线程环境中,能够方便地统一管理多个线程的取消操作。

例如,在一个多线程的数据处理系统中,可能有多个线程同时在处理不同的数据块。我们可以创建一个 std::stop_source 对象,并将其关联的 std::stop_token 传递给每个处理线程。当需要停止整个数据处理过程时,只需调用 std::stop_sourcerequest_stop 方法,所有相关线程就能立即感知到并做出相应的处理。

std::stop_callback:取消时的自定义处理

std::stop_callback 为线程在收到取消请求时执行自定义清理工作或其他必要操作提供了便利。它是一个可以注册到 std::stop_token 上的回调函数。当关联的 std::stop_source 发出取消请求时,所有注册在相应 std::stop_token 上的回调函数都会被依次调用。

例如,在一个涉及文件操作的多线程应用中,某个线程可能在执行过程中打开了多个文件进行读写操作。当该线程收到取消请求时,我们可以通过注册 std::stop_callback 来确保所有打开的文件被正确关闭,避免资源泄漏和数据不一致问题:

std::stop_source fileOpSource;
std::stop_token fileOpToken = fileOpSource.get_token();
std::stop_callback callback(fileOpToken, [] {// 关闭所有打开的文件// 释放相关资源
});

通过这种方式,我们能够在取消请求发生时,对线程的资源进行精确管理,保证程序的健壮性和稳定性。

使用示例

简单的线程取消示例

下面通过一个具体的代码示例来展示 std::jthreadstd::stop_tokenstd::stop_sourcestd::stop_callback 的基本用法:

#include <iostream>
#include <stop_token>
#include <thread>
#include <vector>void worker(int id, std::stop_token stopToken) {for (int i = 0; i < 10; ++i) {std::this_thread::sleep_for(std::chrono::milliseconds(100));if (stopToken.stop_requested()) {std::cout << "Worker " << id << " is requested to stop." << std::endl;return;}std::cout << "Worker " << id << " is running." << std::endl;}
}int main() {std::stop_source stopSource;std::jthread thread1(worker, 1, stopSource.get_token());std::jthread thread2(worker, 2, stopSource.get_token());std::this_thread::sleep_for(std::chrono::seconds(1));stopSource.request_stop();std::cout << "Request stop." << std::endl;return 0;
}

在这个示例中,我们定义了一个 worker 函数,它接受一个线程 ID 和一个 std::stop_token。在 worker 函数内部,通过循环模拟线程的工作过程,并在每次循环中检查 std::stop_token 的状态。如果收到取消请求,线程将打印相应的提示信息并退出。

main 函数中,我们创建了一个 std::stop_source 对象,并通过它获取两个 std::stop_token,分别传递给两个 std::jthread 对象。主线程等待一秒钟后,调用 std::stop_sourcerequest_stop 方法发出取消请求。此时,两个工作线程会检测到 std::stop_token 的状态变化,从而安全地退出。

多任务的协作取消示例

在实际应用中,经常会遇到多个并发任务需要协同取消的场景。以下是一个更具实际意义的示例,展示了如何通过共享 std::stop_source 实现多任务的协作取消:

#include <chrono>
#include <iostream>
#include <stop_token>
#include <syncstream>
#include <thread>
#include <vector>using namespace std::chrono_literals;void download_task(std::stop_token token, int id) {int progress = 0;while (progress < 100) {if (token.stop_requested()) {std::osyncstream(std::cout)<< "[任务 " << id << "] 检测到取消请求, 停止下载.\n";return;}std::this_thread::sleep_for(200ms);progress += 10;std::osyncstream(std::cout)<< "[任务 " << id << "] 当前进度: " << progress << "%\n";}std::osyncstream(std::cout) << "[任务 " << id << "] 下载完成!\n";
}int main() {// 控制多个线程的退出std::stop_source source;std::vector<std::jthread> tasks;for (int i = 0; i < 3; i++) {tasks.emplace_back(download_task, source.get_token(), i);}std::this_thread::sleep_for(1s);std::cout << "[主线程] 取消所有任务.\n";source.request_stop();  // 发出取消信号return 0;
}

在这个示例中,我们定义了一个 download_task 函数,模拟一个下载任务。每个下载任务在执行过程中会定期检查 std::stop_token 的状态。如果收到取消请求,任务将打印提示信息并停止下载。

main 函数中,我们创建了一个 std::stop_source 对象,并利用它启动了三个 std::jthread,每个线程执行一个 download_task。主线程等待一秒钟后,调用 std::stop_sourcerequest_stop 方法,向所有下载任务发出取消请求。所有下载任务在检测到取消请求后,会安全地退出,实现了多任务的协作取消。

结论

C++20 引入的 std::jthreadstd::stop_tokenstd::stop_sourcestd::stop_callback 为线程管理和取消带来了革命性的变化。这些新特性构建了一个更加安全、灵活的线程管理体系,使得开发者能够以更高效、更可靠的方式编写多线程程序。通过合理运用这些特性,我们能够有效避免传统多线程编程中常见的资源泄漏、线程安全等问题,构建出更加健壮、稳定且高性能的并发应用程序。无论是在开发大型服务器端应用、图形处理软件,还是在进行科学计算等领域,这些新的线程管理和取消机制都将发挥重要作用,助力开发者提升程序的质量和性能。

相关文章:

C++20 中线程管理与取消机制的深度剖析

文章目录 std::jthread&#xff1a;更智能的线程管理背景与优势构造函数与 std::stop_token 的集成 std::stop_token、std::stop_source 和 std::stop_callback&#xff1a;灵活的取消机制std::stop_token&#xff1a;取消请求的指示器std::stop_source&#xff1a;取消请求的发…...

Vue3 核心特性解析:Suspense 与 Teleport 原理深度剖析

Vue3 核心特性解析&#xff1a;Suspense 与 Teleport 原理深度剖析 一、Teleport&#xff1a;突破组件层级的时空传送 1.1 实现原理图解 #mermaid-svg-75dTmiektg1XNS13 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-s…...

FPGA——实现LED流水灯

文章目录 一、Quartusll_18.1和VS Code软件的关联二、DE2-115的时钟电路三、流水灯的分层次设计四、总结 一、Quartusll_18.1和VS Code软件的关联 1.先打开Quartus II 软件&#xff0c;然后选择菜单栏“Tools”下的“Options…”。 2.点击“Options…”&#xff0c;在弹出的对…...

Excel 小黑第12套

对应大猫13 涉及金额修改 -数字组 -修改会计专用 VLOOKUP函数使用&#xff08;查找目标&#xff0c;查找范围&#xff08;F4 绝对引用&#xff09;&#xff0c;返回值的所在列数&#xff0c;精确查找或模糊查找&#xff09;双击填充柄就会显示所有值 这个逗号要中文的不能英…...

6、说一下索引失效的场景?【中高频】

索引失效意味着 查询操作 不能利用索引进行数据检索&#xff0c;而是使用 全表扫描&#xff08;也就是 数据库需要从磁盘上读取表的所有数据行&#xff09;&#xff0c;从而导致性能下降&#xff0c;下面一些场景会发生索引失效 对索引使用左或者左右模糊匹配&#xff08;where…...

Noe.js 原生 http 模块 vs Express 框架对比

Noe.js 原生 http 模块 vs Express 框架对比 Noe.js 原生 http 模块 vs Express 框架对比 以下从多个维度对比两种方法&#xff0c;并提供详细示例&#xff0c;帮助初学者理解差异。 1. 基础架构对比 特性原生 http 模块Express 框架核心依赖Node.js 内置模块 (require(htt…...

滚动元素的新api

点击的时候需要双重视图滚动 itemClick(id) {// 滚动到对应位置this.$nextTick(() > {// 找到对应 id 在 initList2 中的索引const index this.initList2.findIndex((item) > item.id Number(id));if (index ! -1) {// 获取所有菜单项const menuItems document.queryS…...

多机调度问题(C语言)

代码如下&#xff1a; #include<stdio.h> #include<stdlib.h>int compare(void* a, void* b)//比较函数&#xff0c;用于qsort按处理时间从大到小排序 {return *(int*)a - *(int*)b; }int LPT(int jobs[], int n, int m)//多机调度问题的LPT算法 {qsort(jobs, n, …...

JS做贪吃蛇小游戏(源码)

一、HTML代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><link rel…...

烽火HG680-KB_海思HI3798MV310_安卓9.0_U盘强刷固件包及注意点说明

之前发布过这个固件包&#xff0c;关于烽火HG680-KA&#xff0f;HG680-KB_海思HI3798MV310_安卓9.0_U盘强刷固件包详细说明一下&#xff0c;汇总总结一些常遇到的情况&#xff0c;这次固件会分开发布&#xff0c;以免混淆。 上一个帖子地址&#xff1a;烽火HG680-KA&#xff0…...

Java数据结构相关知识

文章目录 1. 自动装箱和自动拆箱2. Object的equals方法3. Comparable和Comparator接口 1. 自动装箱和自动拆箱 自动装箱&#xff1a;将基本数据类型自动转换为对应的包装类。自动拆箱&#xff1a;将包装类自动转换为对应的基本数据类型。 显示装箱 int primitiveInt 10; //…...

996引擎 - 红点系统

996引擎 - 红点系统 总结NPC 红点(TXT红点)Lua 红点1. Red_Point.lua2. UI_Ex.lua参考资料以下内容是在三端 lua 环境下测试的 总结 红点系统分几个部分组成。 M2中设置变量推送。 配置红点表。 Envir\Data\cfg_redpoint.xls 2.1. UI元素中找到ID填写 ids 列。 主界面挂载…...

7种数据结构

7种数据结构 顺序表sqlite.hseqlite.c 单链表linklist.clinklist.h 双链表doulinklist.cdoulinklist.h 链式栈linkstack.clinkstack.h 队列SeqQueue.cSeqQueue.h 树tree.c 哈希表hash.c 顺序表 sqlite.h #ifndef __SEQLIST_H__ #define __SEQLIST_H__ typedef struct person…...

Redis的消息队列是怎么实现的

Redis 本身并不是一个专门的消息队列系统,但它的 List、Pub/Sub 和 Stream 数据结构可以用来实现消息队列的功能。以下是 Redis 实现消息队列的几种常见方式: 1. 基于 List 实现消息队列 Redis 的 List 是一个双向链表,支持在头部和尾部进行高效的插入和删除操作,非常适合…...

3.17BUUCTF练习day1

BUUCTF练习day1 [极客大挑战 2019]EasySQL1&#xff08;字符型&#xff0c;账号密码型&#xff0c;get型&#xff09; 判断闭合方式 在用户名输入1‘&#xff0c;此时密码先输入任何数字时&#xff0c;出现语法错误 说明闭合方式为单引号闭合&#xff0c;在判断完闭合方式后…...

【贪心算法】柠檬水找零

1.题目解析 860. 柠檬水找零 - 力扣&#xff08;LeetCode&#xff09; 2.讲解算法原理 分情况讨论 5---》直接收下 10---》找五元&#xff0c;收下 20----》105△ ----》555 由于5元更有用&#xff0c;则尽可能保留5元 3.代码 class Solution {public boolean lemonadeCh…...

黑马跟学.苍穹外卖.Day08

黑马跟学.苍穹外卖.Day08 苍穹外卖-day8课程内容1. 工作台1.1 需求分析和设计1.1.1 产品原型1.1.2 接口设计 1.2 代码导入1.2.1 Controller层1.2.2 Service层接口1.2.3 Service层实现类1.2.4 Mapper层 1.3 功能测试1.3.1 接口文档测试1.3.2 前后端联调测试 1.4 代码提交 2. Ap…...

ABAP语言的动态编程(4) - 综合案例:管理费用明细表

本篇来实现一个综合案例&#xff1a;管理费用明细表。报表在实际项目中&#xff0c;也有一定的参考意义&#xff0c;一方面展示类似的报表&#xff0c;比如管理费用、研发费用等费用的明细&#xff0c;使用业务比较习惯的展示格式&#xff1b;另一方面正好综合运用前面学习的动…...

通过Geopandas进行地理空间数据可视化

目录 引言 安装与导入 数据加载与探索 数据预处理 基本地图可视化 添加其他数据到地图上 空间分析与查询 地图叠加与分组 空间缓冲区 交互式地图可视化 实际应用案例 城市规划 环境监测 结论 引言 在数据科学领域,地理空间数据可视化扮演着至关重要的角色。它不…...

【大语言模型_5】xinference部署embedding模型和rerank模型

一、安装xinference pip install xinference 二、启动xinference ./xinference-local --host0.0.0.0 --port5544 三、注册本地模型 1、注册embedding模型 curl -X POST "http://localhost:5544/v1/models" \ -H "Content-Type: application/json" \…...

CSS3学习教程,从入门到精通,CSS3 选择器权重问题语法知识点及案例代码(5)

CSS3 选择器权重问题语法知识点及案例代码 一、选择器权重概述 在 CSS 中&#xff0c;当多个选择器同时匹配同一个元素时&#xff0c;浏览器会根据选择器的权重来决定哪个样式生效。权重高的选择器的样式会覆盖权重低的选择器的样式。 二、选择器权重计算规则 1. 内联样式&…...

在Vue3中使用Echarts的示例

1.常用-引用ts文件方式 1.1 导出ts文件-一个简单的柱状图 export const baseBarChart (xdata: string[], data: number[][], legendData: string[]) > {if (data.length 0) {return noData;}// 定义颜色数组const color [#00CCCC,#FF9900,#1677DC,#FF6666,#B366FF,#666…...

Three.js 阴影 (Shadow) 知识点整理

阴影主要由 castShadow 和 receiveShadow 控制&#xff0c;并通过不同类型的光源 (DirectionalLight、SpotLight、PointLight) 生成。我们将系统地整理与阴影相关的知识点。 1️⃣ 基础概念 castShadow &#x1f3ad;&#xff1a;物体是否投射阴影。receiveShadow &#x1f3d…...

GHCTF web方向题解

upload?SSTI! import os import refrom flask import Flask, request, jsonify,render_template_string,send_from_directory, abort,redirect from werkzeug.utils import secure_filename import os from werkzeug.utils import secure_filenameapp Flask(__name__)# 配置…...

Logic-RL:小参数qwen模型复现DeepSeek R1 zero

最近很多参照DeepSeek模型训练推理模型的工作,本文将深入 “Logic-RL: Unleashing LLM Reasoning with Rule-Based Reinforcement Learning” 的论文,该论文提出了一种Rule-Based Reinforcement Learning, Logic-RL框架,旨在提升 LLM 的逻辑推理能力,在qwen2.5-7b-instruct…...

CVE-2017-5645(使用 docker 搭建)

介绍: 是一个与 Apache Log4j2 相关的安全漏洞,属于远程代码执行,它可能允许攻击者通过构造恶意的日志信息 在目标系统上执行任意代码 Log4j2 介绍 Log4j2 是 Apache 的一个日志记录工具,属于 Java 应用的日志框架,它是 Log4j 的升级版,性能更好,功能更多.它被广泛的适用于 J…...

conda install 和 pip install 的区别

conda install 和 pip install 是两个常用的包安装命令&#xff0c;但它们在很多方面存在差异。 1. 所属管理系统不同 1.1 conda install conda install 是Anaconda和Miniconda发行版自带的包管理工具 conda 的安装命令。conda 是一个跨平台的开源包管理系统和环境管理系统&…...

蓝桥杯备考:特殊01背包问题——》集合subset

我们划分成两个集合&#xff0c;实际上我们只需要看一部分就行了&#xff0c;也就是从集合的所有元素里挑出恰好满足集合总和的一半儿&#xff0c;当然&#xff0c;如果我们的集合总和是奇数的话&#xff0c;我们是无论如何也挑不出刚好一半儿的&#xff0c;因为我们没有小数&a…...

设计模式之外观模式:原理、实现与应用

引言 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过提供一个统一的接口来简化复杂系统的使用。外观模式隐藏了系统的复杂性&#xff0c;使得客户端可以通过一个简单的接口与系统交互。本文将深入探讨外观模式的原理、实现方式以及实…...

C#设计模式Demo——MVC

设计模式Demo——MVC 1.View1.1页面示例1.2View代码1.3修改界面以及代码 2.Model3.Controller4.数据结构5.枚举类型6.工具类6.1缓存信息6.2扩展类. 文件结构图 1.View 1.1页面示例 1.2View代码 using System; using System.Data; using System.Windows.Forms; using MVC模式…...