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

news2024/10/6 10:29:45

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.coloradmin.cn/o/1463263.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

命令执行 [WUSTCTF2020]朴实无华1

做题&#xff1a; 打开题目 我们用dirsearch扫描一下看看 扫描到有robots.txt&#xff0c;访问一下看看 提示我们 /fAke_f1agggg.php 那就访问一下&#xff0c;不是真的flag bp抓包一下 得到提示&#xff0c; /fl4g.php&#xff0c;访问一下看看 按alt&#xff0c;点击修复文…

IP地理位置查询定位:技术原理与实际应用

在互联网时代&#xff0c;IP地址是连接世界的桥梁&#xff0c;而了解IP地址的地理位置对于网络管理、个性化服务以及安全监控都至关重要。IP数据云将深入探讨IP地理位置查询定位的技术原理、实际应用场景以及相关的隐私保护问题&#xff0c;旨在为读者提供全面了解和应用该技术…

Shopee平台选品原则指南:如何科学有效地进行产品选品

在当今激烈竞争的电商市场中&#xff0c;如何在Shopee平台上选择适合的产品进行销售&#xff0c;是每位卖家都要面对的重要问题。针对这一挑战&#xff0c;我们整理了一些关键原则&#xff0c;帮助卖家们在选品过程中更加科学和有效地进行决策。 先给大家推荐一款shopee知虾数…

一文弄明白KeyedProcessFunction函数

引言 KeyedProcessFunction是Flink用于处理KeyedStream的数据集合&#xff0c;它比ProcessFunction拥有更多特性&#xff0c;例如状态处理和定时器功能等。接下来就一起来了解下这个函数吧 正文 了解一个函数怎么用最权威的地方就是 官方文档 以及注解&#xff0c;KeyedProc…

stm32和嵌入式linux可以同步学习吗?

在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「stm3的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;如果需要使用STM32&#xff0c;建…

LeetCode 0106.从中序与后序遍历序列构造二叉树:分治(递归)——五彩斑斓的题解(若不是彩色的可以点击原文链接查看)

【LetMeFly】106.从中序与后序遍历序列构造二叉树&#xff1a;分治&#xff08;递归&#xff09;——五彩斑斓的题解&#xff08;若不是彩色的可以点击原文链接查看&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/construct-binary-tree-from-inorder-an…

28.云原生之服务网格ServiceMesh和istio

云原生专栏大纲 文章目录 Service Mesh介绍为什么要使用ServiceMesh&#xff1f;Istio介绍istio架构EnvoyIstiod Istio 核心流量管理安全可观测性 Istio 原理istio资源和k8s资源扭转关系istio-ingressgatewayIstio-GatewayVirtualServiceDestinationRule Service Mesh介绍 Se…

【小智好书分享• 第一期】深度学习计算机视觉

目录 一、内容简介二、内页插图三、书籍目录四、粉丝福利中奖名单 &#x1f389;博客主页&#xff1a;小智_x0___0x_ &#x1f389;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f389;系列专栏&#xff1a;好书分享 &#x1f389;代码仓库&#xf…

springboot210基于Springboot开发的精简博客系统的设计与实现

基于Springboot开发的精简博客系统的设计与实现 摘要 当下&#xff0c;正处于信息化的时代&#xff0c;许多行业顺应时代的变化&#xff0c;结合使用计算机技术向数字化、信息化建设迈进。以前企业对于博客信息的管理和控制&#xff0c;采用人工登记的方式保存相关数据&#…

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

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

Salesforce顾问如何拿到更高的薪水?

顾问的角色已经在Salesforce生态系统存在了一段时间&#xff0c;随着Salesforce针对职业发展的Trailhead培训模块的发布&#xff0c;该角色的热度又达到了新的浪潮。越来越多人走上了Salesforce顾问这条职业道路。 当然其薪资水平也非常可观&#xff0c;据调查&#xff0c;美国…

【Linux系统化学习】深入理解匿名管道(pipe)和命名管道(fifo)

目录 进程间通信 进程间通信目的 进程间通信的方式 管道 System V IPC&#xff08;本地通信&#xff09; POSIX IPC&#xff08;网络通信&#xff09; 管道 什么是管道 匿名管道 匿名管道的创建 匿名管道的使用 匿名管道的四种情况 匿名管道的五种特性 命名管道 …

2024第16届全国大学生广告艺术大赛介绍

全国大学生广告艺术大赛介绍 全国大学生广告艺术大赛&#xff08;简称大广赛&#xff09;自2005年第1届至今&#xff0c;遵循“促进教改、启迪智慧、强化能力、提高素质、立德树人”的竞赛宗旨&#xff0c;成功举办了14届共15次赛事&#xff0c;全国共有1857所高校参与其中&am…

#LLM入门|Prompt#1.7_文本拓展_Expanding

输入简短文本&#xff0c;生成更加丰富的长文。 “温度”&#xff08;temperature&#xff09;&#xff1a;控制文本生成的多样性。 一、定制客户邮件 根据客户的评价和其中的情感倾向&#xff0c;使用大语言模型针对性地生成回复邮件。将大大提升客户满意度。 # 我们可以在…

Rust: reqwest库示例

一、单一文件异步 1、cargo.toml [dependencies] tokio { version "1.0.0", features ["full", "tracing"] } tokio-util { version "0.7.0", features ["full"] } tokio-stream { version "0.1" }tr…

《数字化运维路线图》第四部分-数字化运维转型场景 震撼发布!

《数字化运维路线图》系列的压轴之作——《数字化运维转型场景》终于迎来正式发布。这部分内容与《数字化运维组织升级》、《数字化运维转型的标准流程》和《数字化运维转型平台》共同构成了一套完整的数字化运维转型作战蓝图&#xff0c;全方位、多角度地概括了企业如何有效地…

10MARL深度强化学习 Value Decomposition in Common-Reward Games

文章目录 前言1、价值分解的研究现状2、Individual-Global-Max Property3、Linear and Monotonic Value Decomposition3.1线性值分解3.2 单调值分解 前言 中心化价值函数能够缓解一些多智能体强化学习当中的问题&#xff0c;如非平稳性、局部可观测、信用分配与均衡选择等问题…

前端架构: 脚手架之Chalk和Chalk-CLI使用教程

Chalk Chalk 是粉笔的意思, 它想表达的是&#xff0c;给我们的命令行中的文本添加颜色类似彩色粉笔的功能 在官方文档当中&#xff0c;它的 Highlights 核心特性 Expressive API Highly performant No dependencies Ability to nest styles 256/Truecolor color support Auto-…

Android中通过属性动画实现文字轮播效果

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂&#xff0c;风趣幽默"&#xff0c;感觉非常有意思,忍不住分享一下给大家。 &#x1f449;点击跳转到教程 一、创建一个自定义ProvinceView类,具体代码如下 /*** Author: ly* Date: 2024/2/22* D…

【服务器】服务器推荐

一、引言 在数字世界的浪潮中&#xff0c;服务器作为数据存储和处理的基石&#xff0c;其重要性不言而喻。而在这个繁星点点的市场中&#xff0c;雨云以其独特的优势和超高的性价比&#xff0c;逐渐成为众多企业和个人的首选。今天&#xff0c;就让我带你走进雨云的世界&#…