「QT踩坑」中断业务逻辑为死循环的线程

2023-11-08

I. Motivation

在分布式计算模型中,常常会遇到线程间通信(同/异步)的问题,比如 Master 分配任务给 Worker ,后者在完成任务之后,需要向 Master 打报告,请求其处理返回结果,其中 Master 的业务逻辑是一个死循环,它一直在分配任务

这是个很经典的通信过程,如果用 C++ 自带的那套机制去解决这个问题,那么就只能采用共享内存法才能达到向 Master 打报告的目的,因为只有让 Master 跳出死循环,它才能处理其他对象发给它的信号(可以理解为 Master 线程腾不出手)。具体表现为 Worker 在向 Master 打报告时,修改 Master 的循环条件变量,使其跳出分配任务的死循环,转而处理返回结果,待处理完成后再重新进入业务循环中。这样做是可以的,但有失 OOP 的原则( Worker 的手竟然能伸进 Master 的口袋,去修改 Master 的成员变量!这是绝对不允许的)

在 Qt 的世界里有一种更为简单且原始的方法,类似于 OS 中的中断( INTR,Interruption )机制。在 Worker 需要打报告的时候,就调用 Master 对外提供的中断方法即可(无需再跟从前一样修改 Master 的变量),告诉 Master :我 Worker 这有个返回结果想交给你,你先暂停一下,查收一下,确认接收后再继续你的业务循环

在我看来,这是种比较好的交互方式,做到了互不过多打扰。Worker 只知道有 Master 这个人而已,且 Master 也是。两者并不知道对方太多的讯息,更不会发生手贱修改对方成员变量的情况

问题讲完了,下面就看一看具体的代码吧

II. Solution

在经典的分布式计算模型中,一般会有个 Master 节点,它主要负责向空闲的 Worker 分配任务,而且一般是一直分配 or 直到任务队列中已无任务,简单用代码表示,

void 
Master::loop()
{
	while (true) {
		遍历空闲 Worker 集合,挑选空闲 Worker
	
		if(没有空闲 Worker )
	     continue;
		
	   	/* 向编号为 id 的 Worker 发送任务 */
		emit sig_dispatch(id);
	}
}

这就是 Master 任务分配的逻辑,emit 是 Qt 特有的信号槽机制(引以为傲)中的内容,表示 Master 线程要发送一个信号,寻求与其他线程(对 sig_dispatch 信号感兴趣的)通信,通信的内容为变量 id

何为感兴趣?也就是 Qt 的另外一个话题了,即 Connect 机制。在此不详细展开了,Worker 在接收到 sig_dispatch 信号后,就着手开始具体的任务计算了,

void 
Worker::recv_tasking(unsigned id, unsigned sec)
{
  	/* Master 广播的任务是发给自己的,那就接收下来,否则丢弃 */
	if (id_ != id)
		return;
	
	printf("worker %d tasking...%d\n", id_, sec);
	QThread::sleep(sec);
	
  	/* 完成任务后,尝试向 Master 打报告 */
	while (!ma_->intr_taskdone())
		;
  	/* Master 批准之后再向其返回结果 */
	emit sig_taskdone(id, sec);
}

在这里,我用了简单的线程休眠来代替具体的业务计算(不是重点)。在 Worker 的处理流程当中,首先根据 Master 广播的 Packet 中的 id 编号,判别这个 Packet 是否是发给自己的;然后接收任务进行计算;完成任务后,下面是重点

要向 Master 打报告,Worker 线程是知道 Master 的存在的(变量 ma_ ),调用 intr_taksdone() 方法告诉 Master :喂,Master ,我是 Woker x 号,你之前要的计算任务我已经完成了,你要不现在腾个手,接收一下?

为什么要用 while 循环包裹呢?是因为报告一次可能会不成功,所以要一直举手打报告,直到 Master 看见并停下手中的工作。然后再发送 sig_taskdone 信号并将结果返回。其中的方法 intr_taskdone() 是基于 Qt 线程原有机制的屑微封装,

bool 
Master::intr_taskdone() 
{
	mathrd_->requestInterruption();

	return mathrd_->isInterruptionRequested();
}

调用了 QThread::requestInterruption() 方法和 QThread::isInterruptionRequested() 。这两个方法是一对儿,前者大致功能是尝试中断正在运行的线程,对应代码中,即是请求中断线程 mathrd_ ( Master 线程),并返回中断成功与否的情况

如果不用 Qt 线程自带的中断法,那代码就应该写成这样,Master 的业务逻辑不再是死循环,而是条件循环,且这个循环条件抛给 Worker (它可以修改),

void 
Master::loop()
{
  while (!intr) {
    遍历空闲 Worker 集合,挑选空闲 Worker

    if(没有空闲 Worker )
   		continue;

    /* 向编号为 id 的 Worker 发送任务 */
    emit sig_dispatch(id);
  }    
}

变量 intr 控制着 Master 是否要继续分配任务,当其为真时,Master 会跳出循环,这意味此时有 Worker 前来返回结果。Master 在处理完 Worker 返回的结果后,因为需要继续执行分配任务的业务逻辑,所以在接收 sig_taskdone 信号的槽函数 rec_taskdone 中将这套流程串联起来,

void
Master::recv_taskdone()
{
	处理 Worker 的返回结果
  
  	intr = false;
  	loop();
}

Worker 应该这样写,

void 
Worker::recv_tasking(unsigned id, unsigned sec)
{
  	/* Master 广播的任务是发给自己的,那就接收下来,否则丢弃 */
	if (id_ != id)
		return;
	
	printf("worker %d tasking...%d\n", id_, sec);
	QThread::sleep(sec);
	
  	ma_->intr = true;
  
  	/* Master 跳出循环后再向其返回结果 */
	emit sig_taskdone(id, sec);
}

直接修改 Master 的成员变量,虽然也可以进行封装,使其不能直接触碰到 Master 的核心,但是我还是觉得这种方法与 OOP 有点格格不入,传统的 C++ 多线程确实是应该这么写,但是 Qt 线程机制有了更好的选择

而且,当有多个 Worker 的情况下,还需考虑到线程安全的问题,即是在修改条件循环变量 ma_->intr 时还要对其进行上锁,等等一系列的同步操作,真的是让人头疼

III. Evaluation

在 C++ 的世界里,如果想实现线程之间的通信,无非是通过共享内存以及条件变量来实现。这些手段未免太过暴露且繁琐,很容易出错。以至于在现代语言 Go 中采用了 Channel 通信,方便各协程之间的同/异步

Go 中的协程,可以理解为轻量级的线程,但不同于 C/C++ 中的线程。线程是 OS 调度的最小单位,多个线程在 CPU 上是可以并行的;而协程是并发的,一个进程可以有多个协程,但在一个时间点上只能执行某个协程

最直白的例子,就是并行好比三个人同时比赛吃包子,每人都有一个包子,同时啃;而并发就好比一个人同时要吃三个包子,他为了完成任务,只能包子 A 啃一会,然后啃包子 B ,再轮到包子 C ,以此往复,直到吃完三个包子

所以并行和并发乍一听,可能会觉得差不多,都是完成同一件事情。但是仔细想想,两者差别还是很大的。并行是多个核心同时在做事,而且是不同的事,属于是同时赛跑;而并发是一个核心,它轮转做着好几件事,因为轮转的速度比较快,所以人们会有一种错觉:感觉这几件事是同时发生的

都是做事,可能做的很快,并发就被认为是并行,这其实是天大的误会。并发就是并发,并发永远不能代替并行,在很多模拟的场合下,如果错误地用并发的手段代替了并行,将会得到错得离谱的结果(车辆路径问题,Vehicle Routing Problem

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

「QT踩坑」中断业务逻辑为死循环的线程 的相关文章

  • 具有不同大小结构的结构数组的 malloc()

    如果每个结构都包含一个大小不同的字符串数组 那么如何正确地 malloc 一个结构数组 因此每个结构可能有不同的大小 并且不可能 realloc 结构体数量 sizeof 结构体名称 after malloc 初始大小 sizeof 结构名
  • 内联函数/方法

    声明 内联函数必须在调用之前定义 这个说法正确吗 EDIT 该问题最初是德语 内联功能穆森 弗 伊赫雷姆 奥夫鲁夫定义 sein 也许它对任何人都有帮助 是的 它是正确的 但只是部分正确 它可能正确地重新构建如下 内联函数必须在每个翻译单位
  • Subversion 和 Visual Studio 项目的最佳实践

    我最近开始在 Visual Studio 中处理各种 C 项目 作为大型系统计划的一部分 该系统将用于替换我们当前的系统 该系统是由用 C 和 Perl 编写的各种程序和脚本拼凑而成的 我现在正在进行的项目已经达到了颠覆的临界点 我想知道什
  • 在 C++ 中将成对向量转换为两个独立向量的最快方法

    假设我有一个vector of pair
  • 如何尝试/捕获所有异常

    我正在完成由其他人启动的 UWP 应用程序 该应用程序经常崩溃 我总是陷入困境应用程序 at if global System Diagnostics Debugger IsAttached global System Diagnostic
  • C++中delete和delete[]的区别[重复]

    这个问题在这里已经有答案了 可能的重复 C 中的删除与删除 运算符 https stackoverflow com questions 2425728 delete vs delete operators in c 我写了一个包含两个指针的
  • 如何使用MySqlCommand和prepare语句进行多行插入?(#C)

    Mysql 给出了如何使用准备语句和 NET 插入行的示例 http dev mysql com doc refman 5 5 en connector net programming prepared html http dev mysq
  • 如何生成 appsettings..json 文件?

    我有一个 ASP NET Core 2 WebAPI 它将部署在以下环境中 INT QA STAGE 生产环境 基于上述 我需要有appsettings
  • 对 boost 库的依赖项没有完整路径

    我已经成功构建了动态库 依赖于使用自定义前缀构建和安装的 boost 库 b2 install prefix PREFIX 然而 当我跑步时otool L在我的库中 我得到如下输出 libboost regex dylib compatib
  • 从 Code::Blocks 运行程序时出现空白控制台窗口 [重复]

    这个问题在这里已经有答案了 当我尝试在 Code Blocks 中构建并运行新程序时 控制台窗口弹出空白 我必须单击退出按钮才能停止它 它对我尝试过的任何新项目 包括 Hello world 都执行此操作 奇怪的是 它对于我拥有的任何旧项目
  • 从成员函数指针类型生成函子

    我正在尝试简化 通过make fn 预处理参数的函子的生成 通过wrap 对于 arity 的成员函数n 生成函子基本上可以工作 但到目前为止只能通过显式指定成员函数的参数类型来实现 现在我想从它处理的成员函数类型生成正确的函子 struc
  • 预处理后解析 C++ 源文件

    我正在尝试分析c 使用我定制的解析器的文件 写在c 在开始解析之前 我想摆脱所有 define 我希望源文件在预处理后可以编译 所以最好的方法是运行C Preprocessor在文件上 cpp myfile cpp temp cpp or
  • 使用 WF 的多线程应用程序的错误处理模式?

    我正在写一个又长又详细的问题 但只是放弃了它 转而选择一个更简单的问题 但我在这里找不到答案 应用程序简要说明 我有一个 WPF 应用程序 它生成多个线程 每个线程执行自己的 WF 处理线程和 WF 中的错误 允许用户从 GUI 端进行交互
  • 如何随着分辨率的变化自动调整大小和调整表单控件

    我注意到某些应用程序会更改控件的位置以尽可能适应当前的分辨率 例如 如果窗口最大化 则控件的设置方式应使整个 GUI 看起来平衡 是否可以使用 C 在 Visual studio 2010 中制作或实现此功能 Use Dock http m
  • .NET 客户端中 Google 表格中的条件格式请求

    我知道如何在 Google Sheets API 中对值和其他格式进行批量电子表格更新请求 但条件格式似乎有所不同 我已正确设置请求 AddConditionalFormatRuleRequest formatRequest new Add
  • asp.net网格分页的SQL查询

    我在用iBatis and SQLServer 使用偏移量和限制进行分页查询的最佳方法是什么 也许我添加该列ROW NUMBER OVER ORDER BY Id AS RowNum 但这只会阻止简单查询的数据访问 在某些情况下 我使用选择
  • 如何引用解决方案之外的项目?

    我有一个 Visual Studio C 解决方案 其中包含一些项目 其中一个项目需要引用另一个不属于解决方案的项目 一开始我引用了dll
  • C语言声明数组没有初始大小

    编写一个程序来操纵温度详细信息 如下所示 输入要计算的天数 主功能 输入摄氏度温度 输入功能 将温度从摄氏度转换为华氏度 独立功能 查找华氏度的平均温度 我怎样才能在没有数组初始大小的情况下制作这个程序 include
  • OSError: [WinError 193] %1 不是有效的 Win32 应用程序,同时使用 CTypes 在 python 中读取自定义 DLL

    我正在尝试编写用 python 封装 C 库的代码 我计划使用 CTypes 来完成此操作 并使用 Visual Studio 来编译我的 DLL 我从一个简单的函数开始 在 Visual Studio 内的标头中添加了以下内容 然后将其构
  • C#中为线程指定特殊的cpu

    我有 2 个线程 我想告诉其中一个在第一个 cpu 上运行 第二个在第二个 cpu 上运行 例如在具有两个 cpu 的机器中 我怎样才能做到这一点 这是我的代码 UCI UCIMain new UCI Thread UCIThread ne

随机推荐

  • FastCGI介绍

    https blog csdn net liitdar article details 80359467
  • Flutter - Align 对齐与相对定位

    只想简单的调整一个子元素在父元素中的位置的话 使用Align组件会更简单一些 Align Key key this alignment Alignment center 调整内部子控件的位置 this widthFactor 为null时
  • Linux- struct list_head简介

    Linux struct list head struct list head简介 定义一个链表 链表头 添加结点 list add list add tail 从链表中删除一个结点 list del 判断链表是否为空 遍历链表 例 lis
  • 通讯录管理系统(简易)

    include
  • 【AD20学习笔记】PCB封装库的创建

    7 19 这块内容要快点 我一般是立创eda和IC封装网找封装 PCB焊盘 用来焊接器件管脚 管脚序号 和原理图对应 丝印 元器件大致的大小 阻焊 防止被滤油 绝缘 覆盖 1角标识 定位器件正反方向 做封装 一般按表中数据给的最大值 放置焊
  • 机器人走方格 V2【数论】【组合】【费马小定理】

    M N的方格 一个机器人从左上走到右下 只能向右或向下走 有多少种不同的走法 由于方法数量可能很大 只需要输出Mod 10 9 7的结果 Input 第1行 2个数M N 中间用空格隔开 2 lt m n lt 1000000 Output
  • Linux MYSQL8.0数据库安装-->MYSQL主从节点配置-->MYSQL主从切换 详细教程

    目录 一 准备 1 关闭防火墙和SELINUX安全模式 2 上传安装包到 usr local mysql 二 安装MYSQL数据库 1 解压包并开始安装 2 依次安装 3 启动并进入数据库配置 三 MYSQL主从节点配置 1 主机配置 ma
  • 最新版本docker 设置国内镜像源 加速办法

    解决问题 加速 docker 设置国内镜像源 目录 国内加速地址 修改方法 国内加速地址 1 Docker中国区官方镜像 https registry docker cn com 2 网易 http hub mirror c 163 com
  • NOIP错题集锦(不定时更新)

    常识篇 Q 以下不是微软出品的软件是 D A Powerpoint B Word C Excel D Acrobat Reader 解析 A是是微软公司的演示文稿软件 B不用说 C是办公软件 D是Adobe公司 美国Adobe公司 是著名的
  • 用python爬取考研信息网_【高考、考研党的福利】使用Python爬取全国高校及GIS/RS专业信息【附代码和Excel】...

    题外话 前一段时间翻译了一部关于GIS的纪录片 然后发了一篇文章 没想到有这么多人感兴趣 为了让广大GISER知道有这部神片 遂想投稿至GIS相关的专栏 不曾想居然还没人开设 真是 绕树三匝 何枝可依 于是开设了地理信息系统 遥感 定位导航
  • python 词频统计,分词笔记

    Python的中文分词库有很多 常见的有 jieba 结巴分词 THULAC 清华大学自然语言处理与社会人文计算实验室 pkuseg 北京大学语言计算与机器学习研究组 SnowNLP pynlpir CoreNLP pyltp 参考 htt
  • 为什么 Web3 社交将超越其 Web2 同行

    我们最近听到了很多关于 web3 社交媒体平台的消息 但如果你没有跟上 你可能想知道为什么我们已经有了 Twitter Facebook Instagram 等 我们还需要 web3 社交 好吧 这一切都取决于谁拥有权力 在 web2 中
  • 用QT实现一个模拟家居系统

    本系统利用的是Qt Creator 5 12 12制作的 可实现的功能如下 根据用户设定的设备的运行参数生成室内温度 湿度 空气质量随时间的变化情况 若系统是智能的 可根据用户输入的户外温度 湿度的变化生成设备的运行指令 系统的代码量达到了
  • 能在电脑桌面提醒待办事项的日程安排管理软件

    很多上班族越来越习惯找寻一款桌面日程安排软件来管理待办日程 提醒任务事项 常见的比如win7系统的便笺 win10系统的便利贴等 这些桌面记事小工具 往往不需要下载安装 在程序中找到添加到桌面即可使用 在方便快捷的同时 它们也存在着一个不可
  • 【转载】技术向:一文读懂卷积神经网络

    原文地址 http toutiao com a4033463198 tt from sina app news article iid 2585754491 utm medium toutiao android utm campain cl
  • webpack打包工具的使用笔记

    webpack打包工具的使用笔记 一 下载webpack 二 使用方法 三 测试 四 压缩css文件 一 下载webpack 1 系统环境如下 C Users admin gt node v v16 15 1 C Users admin g
  • 源代码编译chrome os

    今天照着官网上的介绍自己编译了一下 这里使用的是自己的一套编译机制 照着做基本上没什么问题 下面是主要步骤 需要注意的是编译的时候需要下载很多软件包 所以网络必须要好 就和该操作系统本身一样 没网络 再好的戏也出不来 1 安装depot t
  • DOCKER安装SEATA注册到NACOS

    因为总是多多少少的会出现问题 所以我自行搭建成功 跳过所有坑之后写了个博客 此处没有使用集群 说明 请创建对应seata所需的数据库 将seata源码中的sql执行进去 1 使用最新的seata和nacos以及mysql5 7版本 基于ce
  • 【react】props总结

    每个组件对象都会有props属性 组件标签内的所有属性都保存在props中 props是通过标签属性从组件外向组件内传递变化的数据 注意 组件内部不用修改props数据 props是只读的
  • 「QT踩坑」中断业务逻辑为死循环的线程

    文章目录 I Motivation II Solution III Evaluation I Motivation 在分布式计算模型中 常常会遇到线程间通信 同 异步 的问题 比如 Master 分配任务给 Worker 后者在完成任务之后