使用 C++23 协程实现第一个 co_await 同步风格调用接口--Qt计算文件哈希值

C++加入了协程 coroutine的特性,一直没有动手实现过。看了网上很多文章,已经了解了协程作为“可被中断和恢复的函数”的一系列特点。在学习过程中,我发现大多数网上的例子,要不就是在main()函数的控制台程序里演示yeild,await, resume的特性,要不就是讲述很多概念,很少有演示协程究竟如何把异步变成同步调用的。本次,我们就通过一个简单的计算文件哈希值的例子,来演示如何进行协程操作。

1. 原始的哈希值计算

假设存在一个最简单的哈希计算需求,要计算一个大文件的指纹。我们很容易实现一个演示算法:

void DlgCT::on_pushButton_normal_clicked(){QFile fp(filename);char buf[1024];unsigned long long hashfile = 0;if (fp.open(QIODevice::ReadOnly)){int rlen = fp.read(buf,1024);while (rlen>0){for (int i=0;i<rlen;++i){unsigned char c = hashfile>>56;hashfile <<=8;hashfile ^= (buf[i] ^ c );}rlen = fp.read(buf,1024);//假多线程,也可以看做是Qt的有栈协程,人为释放资源QCoreApplication::processEvents();}}
}

上面的代码,在文件比较大时,如果没有‘’QCoreApplication::processEvents();”显然会阻塞界面,导致按钮弹不起来,界面卡死。当然,可以通过适时调用QCoreApplication::processEvents();保持消息循环。这是一种假多线程,也可以看做是Qt的有栈协程,人为释放资源让给其他消息。

2. 异步计算改造

为了不阻塞主界面,传统上喜欢使用另一个线程来处理算法,并在完成后通知主线程。有一个处理类:

class fileDealer : public QObject
{Q_OBJECT
public:explicit fileDealer(QObject *parent = nullptr);//dealFile 计算哈希,存储在 result 里void dealFile(QString filename);
public:QByteArray result;
private:std::thread * m_pThread = nullptr;
signals:void sig_done();
};void fileDealer::dealFile(QString filename)
{m_pThread = new std::thread([filename,this]()->void{		QFile fp(filename);char buf[1024];unsigned long long hashfile = 0;if (fp.open(QIODevice::ReadOnly)){			int rlen = fp.read(buf,1024);while (rlen>0){for (int i=0;i<rlen;++i){unsigned char c = hashfile>>56;hashfile <<=8;hashfile ^= (buf[i] ^ c );}				rlen = fp.read(buf,1024);}}emit sig_done();		});
}

这个类会开启一个独立的线程,做完后触发信号sig_done。上述代码是主干功能,相应的new,delete维护部分略去。如此一来,则需要在按钮响应函数里改造异步调用:

void DlgCT::on_pushButton_thread_clicked()
{fileDealer * dealer = new fileDealer(this);connect(dealer,&fileDealer::sig_done,[dealer,this]()->void{dealer->deleteLater();});dealer->dealFile(ui->lineEdit_file->text());
}

即可完成非阻塞处理。

3. 使用协程 co_await 同步风格编程

如果使用C++协程,当然希望直接可以实现同步风格的异步调用:

void DlgCT::on_pushButton_file_clicked()
{dealFile(ui->lineEdit_file->text());
}
FileTask DlgCT::dealFile(QString filename)
{QByteArray res = co_await awDealFile(filename);//注意!若协程库开发不周到,此时有可能已经不是在主界面线程了!一定注意操作界面控件的线程安全性。showMsg(res);
}

在 co_await 语句后,返回主消息循环,此时定时器等依旧顺利工作。直到文件计算完毕后,才返回 showMsg(res);。为了达到上述效果,需要如下两步骤:

3.1 添加协程代码

首先,添加协程返回对象结构体. 本示例只使用 co_await关键词,所以大部分的必备函数入口都是默认值,啥也不做。

/*!* \brief The FileTask class	协程结构体*/
struct FileTask
{struct promise_type;using handle_type = std::coroutine_handle<promise_type>;FileTask(handle_type h){}FileTask(FileTask&& s){}struct promise_type {promise_type() = default;~promise_type() = default;auto get_return_object() noexcept {return FileTask{handle_type::from_promise(*this)};}auto initial_suspend() noexcept {//一创建立刻执行return std::suspend_never{};}auto final_suspend() noexcept {return std::suspend_always{};}void unhandled_exception() {exit(1);}void return_void(){}};};

3.2 创建 await 辅助类

关键实现await功能的就是下面这个类:

/*!* \brief The awDealFile class	协程 await 对象*/
class awDealFile : public QObject
{Q_OBJECT
public:awDealFile(QString filename, QObject *parent = nullptr):QObject(parent),m_fn(filename),m_pDealer(new fileDealer){//处理完毕的信号,会在处理线程里发出,所以用QueuedConnection确保协程返回时,保持线程不变。QObject::connect(m_pDealer,&fileDealer::sig_done,this, &awDealFile::slot_done,Qt::QueuedConnection);}~awDealFile(){if (m_pDealer)m_pDealer->deleteLater();m_pDealer = nullptr;}bool await_ready() {	return false;	}/*!* \brief await_resume	这个函数的返回值决定了 await 关键词可以返回什么类型的东西* \return 哈希结果*/QByteArray await_resume() {return m_pDealer->result;}/*!* \brief await_suspend	co_await 时,会调用这个函数。此时,启动处理,并在处理完毕后resume* \param h*/void await_suspend(FileTask::handle_type h) {hd = h;//处理m_pDealer->dealFile(m_fn);}
private slots:void slot_done(){if (hd)	hd.resume();}
private:QString m_fn;fileDealer * m_pDealer = nullptr;FileTask::handle_type hd;
};

有了上述代码,则可实现同步调用。

4. 关于线程切换的风险

协程的co_await 实际上提供了一个无栈的暂停-恢复框架。关键是要在确保处理完毕后,及时调用 resume 恢复执行。值得注意的是,对于从一个 std::thread内直接 resume的方法,会导致线程切换!此行为务必引起重视。在哪个线程调用的resume,协程函数恢复后,就回到哪个线程。这对操作GUI控件的代码带来了隐晦的风险!

可以看到,在例子里使用Qt的跨线程队列槽 (Qt::QueuedConnection)确保恢复后的协程执行序依旧位于主线程。虽然在实验中,多线程操作控件似乎也没有报错,但这不是推荐的控件操作方法。

	//处理完毕的信号,会在处理线程里发出,所以用QueuedConnection确保协程返回时,保持线程不变。QObject::connect(m_pDealer,&fileDealer::sig_done,this, &awDealFile::slot_done,Qt::QueuedConnection);void slot_done(){if (hd)	hd.resume();}

5. 范例代码

范例代码参考:

https://gitcode.net/coloreaglestdio/qtcpp_demo/-/tree/master/qt_coro_test

在 MSYS2 Qt6 /Linux下编译通过。
范例工程

6. 体会-协程用的香,协程库开发一点也不简单

上述把一个异步操作变成同步,其实就是一个语法糖,背后还是多线程。如果一下处理1000个文件,开启1000个线程是不合理的,需要管理一个线程池,并管理请求队列,保证机械硬盘在一个合理的并发规模下运转。

推而广之,协程能够发挥co_await的功效,仰赖于协程库背后的管理机制,如系统层面的异步回调(如socket)、库层面的线程池。一个简单的 co_await背后的代码量不容小觑。

比较全面的协程改造的例子,参考这个基于Qt 的协程库 https://qcoro.dvratil.cz/,可以看见为了这一句“co_await”,库开发者要做的工作。

此外,作为使用者,要搞清楚语法糖背后创建了哪些对象,生命周期如何,前后线程是不是一致,才能不踩坑。越是表面看起来无比清晰的代码,踩坑越是惊心动魄。所以如果是基于Qt这样的成熟框架,有Lambda槽回调,大可不必在生产环境激进地尝试协程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/262469.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Jmeter基础(2) 目录介绍

目录 Jmeter目录介绍bin目录docsextrasliblicensesprintable_docs Jmeter目录介绍 在学习Jmeter之前&#xff0c;需要先对工具的目录有些了解&#xff0c;也会方便后续的学习 bin目录 examplesCSV目录中有CSV样例jmeter.batwindow 启动文件jmeter.shMac/linux的启动文件jmete…

《最新出炉》系列初窥篇-Python+Playwright自动化测试-19-处理鼠标拖拽-中篇

1.简介 上一篇中&#xff0c;主要是介绍了拖拽的各种方法的理论知识以及实践&#xff0c;今天宏哥讲解和分享一下划取字段操作。例如&#xff1a;需要在一堆log字符中随机划取一段文字&#xff0c;然后右键选择摘取功能。 2.划取字段操作 划取字段操作就是在一段文字中随机选…

分布式id实战

目录 常用方式 特征 潜在问题 信息安全 高性能 UUID 雪花算法 数据库生成 美团Leaf方案 Leaf-segment 数据库方案 Leaf-snowflake 方案 常用方式 uuid雪花算法数据库主键 特征 全局唯一趋势递增信息安全 潜在问题 信息安全 如果id连续递增, 容易被爬虫, 批量下…

记录 使用FFMPEG 笔记本摄像头推流

一、使用 FFMPEG 测试摄像头拉流显示 # 获取摄像头名称 ffmpeg -list_devices true -f dshow -i dummy# 我笔记本上的摄像头名称如下 device_pnp_\\?\usb#vid_0408&pid_1020&mi_00#6&199e90f7&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global# 使…

蓝桥杯DP算法——区间DP(C++)

根据题意要求的是将石子合并的最小权值&#xff0c;我们可以根据DP思想使用二维数组f[i,j]来存放所有从第i堆石子到第j堆石子合并成一堆石子的合并方式。 然后由第二个图所示&#xff0c;我们可以将i到j区间分成两个区间&#xff0c;因为将i到j合并成一个区间的前一步一定是合…

戏曲文化苑|戏曲文化苑小程序|基于微信小程序的戏曲文化苑系统设计与实现(源码+数据库+文档)

戏曲文化苑小程序目录 目录 基于微信小程序的戏曲文化苑系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、微信小程序前台 2、管理员后台 &#xff08;1&#xff09;戏曲管理 &#xff08;2&#xff09;公告信息管理 &#xff08;3&#xff09;公告类型管理…

京东前端笔试(附答案解答)

引言 我目前本科大四&#xff0c;正在春招找前端&#xff0c;有大厂内推的友友可以聊一聊&#xff0c;球球给孩子的机会吧。 我整理了一份10w字的前端技术文档&#xff1a;https://qx8wba2yxsl.feishu.cn/docx/Vb5Zdq7CGoPAsZxMLztc53E1n0k?fromfrom_copylink &#xff0c;对…

RabbitMQ监控方法以及核心指标

RabbitMQ监控方法以及核心指标 1. 监控指标采集2. 使用rabbimq插件采集指标2.1 3.8.0之前版本&#xff0c;使用外部插件暴露2.2 3.8.0之后版本&#xff0c;使用内置插件暴露 3. 使用rabbitmq_exporter采集指标3.1 部署rabbitmq_exporter3.2 prometheus采集rabbitmq_exporter的暴…

MacBook的nginx出现13: Permission denied 的问题分析和解决办法

同样的项目代码&#xff0c;电脑从Windows更换到了MacBook&#xff0c;发现网站的样式都没有了&#xff0c;直接访问CSS文件 http://crm.ms-test.cc/toolstatic/css/bootstrap.min.css 发现无法访问。查看Nginx错误日志&#xff1a; 说明是nginx没有权限访问这个CSS文件&#…

LeetCode--代码详解 59. 螺旋矩阵 II

59. 螺旋矩阵 II 题目 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&#xff1a; 输入&a…

Stable Diffusion 模型分享:Indigo Furry mix(人类与野兽的混合)

本文收录于《AI绘画从入门到精通》专栏,专栏总目录:点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十

Recorder 实现语音录制并上传到后端(兼容PC和移动端)

Recorder 首页&#xff1a;https://github.com/xiangyuecn/Recorder 一、安装 npm install recorder-core二、代码部分 1. HTML页面 <template><div><el-inputv-model"ttsText"type"textarea"placeholder"请输入内容"><…

C#写的一个计算DCI-P3色域和SRGB的小工具

文章最后附带分享链接与提取码 方便需要测试屏幕的小伙伴&#xff0c;只需要输入RGB就能得到覆盖率与比率&#xff0c;W计算色温&#xff0c;不测也要写上&#xff0c;不然会报错 链接&#xff1a;https://pan.baidu.com/s/1wdmAwmwiXjNvn1tGsvy0HA 提取码&#xff1a;1234

基于SpringBoot的在线拍卖系统设计与实现(源码+调试+LW+PPT)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。今天给大家介绍一篇基于SpringBoot的在线拍…

HarmonyOS—使用预览器查看应用/服务效果

DevEco Studio为开发者提供了UI界面预览功能&#xff0c;可以查看应用/服务的UI界面效果&#xff0c;方便开发者随时调整界面UI布局。预览器支持布局代码的实时预览&#xff0c;只需要将开发的源代码进行保存&#xff0c;就可以通过预览器实时查看应用/服务运行效果&#xff0c…

Kotlin filterIsInstance filterNotNull forEach

Kotlin filterIsInstance filterNotNull forEach fun main(args: Array<String>) {val i1 MyItem(1, 1)val i2: MyItem? nullval i3: Int 3val i4 "4"val i5 nullval i6 MyItem(6, 6)val list mutableListOf<Any?>(i1, i2, i3, i4, i5, i6)lis…

学习Markdown

https://shadows.brumm.af 欢迎使用Markdown编辑器 你好&#xff01; 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章&#xff0c;了解一下Markdown的基本语法知识。 新的改变 我们对Markdown编辑器进行了一些…

Flink中的双流Join

1. Flink中双流Join介绍 Flink版本Join支持类型Join API1.4innerTable/SQL1.5inner,left,right,fullTable/SQL1.6inner,left,right,fullTable/SQL/DataStream Join大体分为两种&#xff1a;Window Join 和 Interval Join 两种。 Window Join又可以根据Window的类型细分为3种…

Video generation models as world simulators-视频生成模型作为世界模拟器

原文地址&#xff1a;Video generation models as world simulators 我们探索在视频数据上进行大规模生成模型的训练。具体来说&#xff0c;我们联合训练文本条件扩散模型&#xff0c;同时处理不同持续时间、分辨率和长宽比的视频和图像。我们利用一个在视频和图像潜在编码的时…

六、回归与聚类算法 - 岭回归

目录 1、带有L2正则化的线性回归 - 岭回归 1.1 API 2、正则化程度的变化对结果的影响 3、波士顿房价预测 线性回归欠拟合与过拟合线性回归的改进 - 岭回归分类算法&#xff1a;逻辑回归模型保存与加载无监督学习&#xff1a;K-means算法 1、带有L2正则化的线性回归 - 岭回…