事件循环与线程 一

2023-11-09

 

初次读到这篇文章,译者感觉如沐春风,深刻体会到原文作者是花了很大功夫来写这篇文章的,文章深入浅出,相信仔细读完原文或下面译文的读者一定会有收获。

由于原文很长,原文作者的行文思路是从事件循环逐渐延伸到线程使用的讨论,译者因时间受限,暂发表有关事件循环的译文。另一半线程实用的译文将近期公布。文中有翻译不当的地方,还请见谅。

 

介绍

线程是qt channel里最流行的讨论话题之一。许多人加入了讨论并询问如何解决他们在运行跨线程编程时所遇到的问题。

快速检阅一下他们的代码,在发现的问题当中,十之八九遇到得最大问题是他们在某个地方使用了线程,而随后又坠入了并行编程的陷阱。Qt中创建、运行线程的“易用”性、缺乏相关编程尤其是异步网络编程知识或是养成的使用其它工具集的习惯、这些因素和Qt的信号槽架构混合在一起,便经常使得人们自己把自己射倒在了脚下。此外,Qt对线程的支持是把双刃剑:它即使得你在进行Qt多线程编程时感觉十分简单,但同时你又必须对Qt所新添加许多的特性尤为小心,特别是与QObject的交互。

本文的目的不是教你如何使用线程、如何适当地加锁,也不是教你如何进行并行开发或是如何写可扩展的程序;关于这些话题,有很多好书,比如这个链接给的推荐读物清单.  这篇文章主要是为了向读者介绍Qt 4的事件循环以及线程使用,其目的在于帮助读者们开发出拥有更好结构的、更加健壮的多线程代码,并回避Qt事件循环以及线程使用的常见错误。

先决条件

考虑到本文并不是一个线程编程的泛泛介绍,我们希望你有如下相关知识:

  • C++基础;
  • Qt 基础:QOjbects , 信号/槽,事件处理;
  • 了解什么是线程、线程与进程间的关系和操作系统;
  • 了解主流操作系统如何启动、停止、等待并结束一个线程;
  • 了解如何使用mutexes, semaphores 和以及wait conditions 来创建一个线程安全/可重入的函数、数据结构、类。

本文我们将沿用如下的名词解释,即

  • 可重入 一个类被称为是可重入的:只要在同一时刻至多只有一个线程访问同一个实例,那么我们说多个线程可以安全地使用各自线程内自己的实例。 一个函数被称为是可重入的:如果每一次函数的调用只访问其独有的数据(译者注:全局变量就不是独有的,而是共享的),那么我们说多个线程可以安全地调用这个函数。 也就是说,类和函数的使用者必须通过一些外部的加锁机制来实现访问对象实例或共享数据的序列化。
  • 线程安全  如果多个线程可以同时使用一个类的对象,那么这个类被称为是线程安全的;如果多个线程可以同时使用一个函数体里的共享数据,那么这个函数被称为线程安全的。

(译者注:   更多可重入(reentrant)和t线程安全(thread-safe)的解释:  对于类,如果它的所有成员函数都可以被不同线程同时调用而不相互影响——即使这些调用是针对同一个类对象,那么该类被定义为线程安全。 对于类,如果其不同实例可以在不同线程中被同时使用而不相互影响,那么该类被定义为可重入。在Qt的定义中,在类这个层次,thread-safe是比reentrant更严格的要求)

事件与事件循环

Qt作为一个事件驱动的工具集,其事件和事件派发起到了核心的作用。本文将不会全面的讨论这个话题,而是会聚焦于与线程相关的一些关键概念。想要了解更多的Qt事件系统专题参见 (这里[doc.qt.nokia.com]这里 [doc.qt.nokia.com] ) (译者注:也欢迎参阅译者写的博文:浅议Qt的事件处理机制一

一个Qt的事件是代表了某件另人感兴趣并已经发生的对象;事件与信号的主要区别在于,事件是针对于与我们应用中一个具体目标对象(而这个对象决定了我们如何处理这个事件),而信号发射则是“漫无目的”。从代码的角度来说,所有的事件实例是QEvent [doc.qt.nokia.com]的子类,并且所有的QObject的派生类可以重载虚函数QObject::event(),从而实现对目标对象实例事件的处理。

事件可以产生于应用程序的内部,也可以来源于外部;比如:

  • QKeyEvent和QMouseEvent对象代表了与键盘、鼠标相关的交互事件,它们来自于视窗管理程序。
  • 当计时器开始计时,QTimerEvent 对象被发送到QObject对象中,它们往往来自于操作系统。
  • 当一个子类对象被添加或删除时,QChildEvent对象会被发送到一个QObject对象重,而它们来自于你的应用程序内部

对于事件来讲,一个重要的事情在于它们并没有在事件产生时被立即派发,而是列入到一个事件队列Event queue)中,等待以后的某一个时刻发送。分配器(dispatcher )会遍历事件队列,并且将入栈的事件发送到它们的目标对象当中,因此它们被称为事件循环(Event loop). 从概念上讲,下段代码描述了一个事件循环的轮廓:

 

 

我们是通过运行QCoreApplication::exec()来进入Qt的主体事件循环的;这会引发阻塞,直至QCoreApplication::exit() 或者 QCoreApplication::quit() 被调用,进而结束循环。

这个“wait_for_more_events()” 函数产生阻塞,直至某个事件的产生。 如果我们仔细想想,会发现所有在那个时间点产生事件的实体必定是来自于外部的资源(因为当前所有内部事件派发已经结束,事件队列里也没有悬而未决的事件等待处理),因此事件循环被这样唤醒:

  • 视窗管理活动(键盘按键、鼠标点击,与视窗的交互等等);
  • socket活动 (有可见的用来读取的数据或者一个可写的非阻塞Socket, 一个新的Socket连接的产生);
  • timers (即计时器开始计时)
  • 其它线程Post的事件(见后文)。

Unix系统中,视窗管理活动(即X11)通过Socket(Unix 域或者TCP/IP)通知应用程序(事件的产生),因为客户端使用它们与X服务器进行通讯。 如果我们决定用一个内部的socketpair(2)来实现跨线程的事件派发,那么视窗管理活动需要唤醒的是

  • sockets;
  • timers;

这也是select(2) 系统调用所做的: 它为视窗管理活动监控了一组描述符,如果一段时间内没有任何活动,它会超时。Qt所要做的是把系统调用select的返回值转换为正确的QEvent子类对象,并将其列入事件队列的栈中,现在你知道事件循环里面装着什么东西了吧:)

为什么需要运行事件循环?

下面的清单并不全,但你会有一幅全景图,你应该能够猜到哪些类需要使用事件循环。

  • Widgets 绘图与交互: 当派发QPaintEvent事件时,QWidget::paintEvent() 将会被调用。QPaintEvent可以产生于内部的QWidget::update() ,也可以产生于外部的视窗管理(比如,一个显示被隐藏的窗口)。同样的,各种各样的交互(键盘、鼠标等)所对应的事件均需要事件循环来派发。
  • Timers: 长话短说,当select(2)或相类似的调用超时时,计时器开始计时,因此需要让Qt通过返回事件循环让那些调用为你工作。
  • Networking: 所以底层的Qt网络类(QTcpSocket, QUdpSocket, QTcpServer等)均被设计成异步的。当你调用read()时,它们仅仅是返回已经可见的数据而已; 当你调用write()时,它们仅是将写操作列入执行计划表待稍后执行。 真正的读写仅发生于事件循环返回的时候。 请注意虽然Qt网络类提供了相应的同步方法(waitFor* 一族),但它们是不被推荐使用的,原因在于他们阻塞了正在等待的事件循环。向QNetworkAccessManager这样的上层类,并不提供同步API 而且需要事件循环。

阻塞事件循环

在讨论为什么你永远都不要阻塞事件循环之前,让我们尝试着再进一步弄明白到底“阻塞”意味着什么。假定你有一个按钮widget,它被按下时会emit一个信号;还有一个我们下面定义的Worker对象连接了这个信号,而且这个对象的槽做了很多耗时的事情。当你点击完这个按钮后,从上之下的函数调用栈如下所示:

 

 

 

在main()中,我们通过调用QApplication::exec() (如上段代码第2行所示)开启了事件循环。视窗管理者发送了鼠标点击事件,该事件被Qt内核捕获,并转换成QMouseEvent ,随后通过QApplication::notify() (notify并没有在上述代码里显示)发送到我们的widget的event()方法中(第4行)。因为Button并没有重载event(),它的基类QWidget方法得以调用。 QWidget::event() 检测出传入的事件是一个鼠标点击,并调用其专有的事件处理器,即Button::mousePressEvent() (第5行)。我们重载了 mousePressEvent方法,并发射了Button::clicked()信号(第6行),该信号激活了我们worker对象中十分耗时的Worker::doWork()槽(第8行)。(译者注:如果你对这一段所描述得函数栈的更多细节,请参见浅议Qt的事件处理机制一

当worker对象在繁忙的工作时,事件循环在做什么呢? 你也许猜到了答案:什么也没做!它分发了鼠标点击事件,并且因等待event handler返回而被阻塞。我们阻塞了事件循环,也就是说,在我们的doWork()槽(第8行)干完活之前再不会有事件被派发了,也再不会有pending的事件被处理。

当事件派发被就此卡住时,widgets 也将不会再刷新自己(QPaintEvent对象将在事件队列里静候),也不能有进一步地与widgets交互的事件发生,计时器也不会在开始计时,网络通讯也将变得迟钝、停滞。更严重的是,许多视窗管理程序会检测到你的应用不再处理事件,从而告诉用户你的程序不再有响应(not responding). 这就是为什么快速的响应事件并尽可能快的返回事件循环如此重要的原因

强制事件循环

那么,对于需要长时间运行的任务,我们应该怎么做才会不阻塞事件循环? 一个可行的答案是将这个任务移动另一个线程中:在一节,我们会看到如果去做。一个可能的方案是,在我们的受阻塞的任务中,通过调用QCoreApplication::processEvents() 人工地强迫事件循环运行。QCoreApplication::processEvents() 将处理所有事件队列中的事件并返回给调用者。

另一个可选的强制地重入事件的方案是使用QEventLoop [doc.qt.nokia.com] 类,通过调用QEventLoop::exec() ,我们重入了事件循环,而且我们可以把信号连接到QEventLoop::quit() 槽上使得事件循环退出,如下代码所示:

 

QNetworkReply 没有提供一个阻塞式的API,而且它要求运行一个事件循环。我们进入到一个局部QEventLoop,并且当回应完成时,局部的事件循环退出。

当重入事件循环是从“其他路径”完成的则要非常小心:它可能会导致无尽的递归循环!让我们回到Button这个例子。如果我们再在doWork() 槽里面调用QCoreApplication::processEvents() ,这时用户又一次点击了button,那么doWork()槽将会再次被调用:

 

 

 

一个快速并且简单的临时解决办法是把QEventLoop::ExcludeUserInputEvents 传递给QCoreApplication::processEvents(), 也就是说,告诉事件循环不要派发任何用户输入事件(事件将简单的呆在队列中)。

同样地,使用一个对象的deleteLater() 来实现异步的删除事件(或者,可能引发某种“关闭(shutdown)”的任何事件)则要警惕事件循环的影响。 (译者注:deleteLater()将在事件循环中删除对象并返回)

 

 

可以看到,我们并没有用QCoreApplication::processEvents()  (从Qt 4.3之后,删除事件不再被派发 ),但是我们确实用到了其他的局部事件循环(像我们QEventLoop 启动的这个循环,或者下面将要介绍的QDialog::exec())。

切记当我们调用QDialog::exec()或者 QMenu::exec()时,Qt进入了一个局部事件循环。Qt 4.5 以后的版本,QDialog 提供了QDialog::open() 方法用来再不进入局部循环的前提下显示window-modal式的对话框

 

 

至此事件循环(event loop)的讨论告一段落,接下来,我们要讨论Qt的多线程:事件循环与线程二

 

请尊重原创作品和译文。转载请保持文章完整性,并以超链接形式注明原始作者主站点地址,方便其他朋友提问和指正。 

 

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

事件循环与线程 一 的相关文章

  • 如何管理返回到 QML 的动态分配的 QObject 的生命周期?

    我有这个代码 QVariant componentFromCode QString code QQmlComponent component new QQmlComponent engine engine gt setObjectOwner
  • Mac OS X 文件关联有效,但文件图标未更改

    我使用 Qt 5 3 2 开发了一个 Mac 应用程序 该应用程序处理具有特定扩展名的文件 比方说 xyz 我创建了一个名为 XYZ icns 的图标文件 并将其添加到我的应用程序包资源文件夹中 MyApp app Contents Res
  • iOS 模拟器无法正确刷新

    我尝试模拟一个在 Xcode 9 中创建的非常非常简单的应用程序 我尝试在装有 iOS 11 2 的 iPhone6 的 iOS 模拟器中模拟它 我还测试了其他设备 结果相同 在真实设备上 该应用程序可以按预期运行 但在模拟器上却没有 我希
  • Qt + win32 + mingw 上的原生 Windows API 链接问题

    我正在尝试使用 mingw 工具集将本机 Windows API 与 Qt 结合使用 部分功能存在链接问题 会发生什么 这是 mingw 名称修改的错误吗 ifdef Q WS WIN HWND hwnd QWidget winId HDC
  • 对齐坐标系

    Let s say I have 2 coordinate systems as it is shown in image attached 如何对齐这个坐标系 我知道我需要将第二个坐标系围绕 X 平移 180 度 然后将其平移到第一个坐标
  • 更改 WinForms 按钮突出显示颜色

    I found 这一页 https stackoverflow com questions 9260303 how to change menu hover color winforms 其中概述了如何更改 MenuStrip 及其项目的呈
  • 使用“onclick”和 JavaScript 获取按钮上的 X/Y 坐标

    我是一名 JavaScript 初学者 正在寻找一种方法来获取单击按钮时的 x 和 y 坐标 这适用于 Opera IE9 和 Chrome 但我无法让它在 Firefox 中工作 到目前为止 这是我的代码 JavaScript 中的函数
  • 如何左对齐 Angular Material 拉伸 md 按钮中的文本?

    无需过多修改我自己的 CSS 是否有一个属性或配置可以用来左对齐文本md按钮 https material angularjs org latest api material components button directive mdBu
  • 安装多个版本的 Qt 库

    我在windows中安装了QtSDK 它的Qt库版本是4 7 0 现在我想为 mingw 和 VS2008 安装 Qt 库版本 4 8 2 我怎样才能做到这一点 如何向QtCreator引入多个版本 注意 我已经从以下位置下载了库http
  • 如何获取 QIcon 的文件/资源​​路径

    假设我做了这样的事情 QIcon myIcon resources icon ico 我稍后如何确定该图标的路径 例如 QString path myIcon getPath 问题是 没有getPath 会员 我找不到类似的东西 但肯定有办
  • Qt/c++ 随机字符串生成[重复]

    这个问题在这里已经有答案了 我正在创建一个应用程序 需要生成多个随机字符串 几乎就像一个由一定长度的 ASCII 字符组成的唯一 ID 这些字符混合有大写 小写 数字字符 有没有 Qt 库可以实现这一点 如果没有 在纯 C 中生成多个随机字
  • 如何在 C++ 运行时更改 QML 对象的属性?

    我想在运行时更改 QML 对象的文本 我尝试如下 但文本仍然为空 这是后端类 class BackEnd public QObject Q OBJECT Q PROPERTY QString userFieldText READ userF
  • 让按钮更容易点击

    我有一个按钮 在某些手机上由于尺寸太大而很难点击 但让它变大会破坏布局 可以向视图解释它有一个比其可见区域更大的 点击框 吗 不确定这是否有帮助 如果您使用没有背景的 ImageButton 并设置 Padding 值 您的按钮将具有更大的
  • Qt中如何获取鼠标在屏幕上的位置?

    我想获取屏幕上的鼠标坐标 我怎样才能在 Qt 中做到这一点 在 Windows 上 使用 C 我正在做类似答案中建议的事情对于这个问题 https stackoverflow com q 11737665 1420197 正如文档所述 QC
  • 当 AngularJS 表单无效时禁用提交按钮

    我的表格是这样的
  • Qt 创建者 + MITK (Linux)

    我正在尝试使用MITK 与 Qt Creator 我已经通过 ccmake 成功编译并使用了 VTK 和 ITK 我已经编译了 MITK超级建造模式 它下载 CTK VTK ITK 等 然后我就配置好了 我已经用 make 编译了 大约两个
  • Qt WinRT 应用程序无法访问文件权限被拒绝

    我需要使用 Qt 和 FFMPEG 开发 WinRT 应用程序 我根据指令构建了 WinRT 的 ffmpeghere https github com Microsoft FFmpegInterop我可以将库与我的项目链接起来 现在我需要
  • QTcpSocket 有时不发送数据

    我有两个 QT 应用程序 一个应用程序可以被认为保存了大数据 它每秒向第二个应用程序发送大约 10 KB 的数据块 之前我尝试使用QUdpSocket来传输数据 但由于MTU限制在2 5K左右 并且需要自己分割和重新组合数据 所以我改用QT
  • Qt - 如何粘合两个窗口并将它们移动在一起?

    就像qmmp Qt 音乐播放器ui设计一样 这两个或三个窗口实际上在同一个窗口中 因为只有一个dock图标 并且这些窗口可以一起移动并相互附着 我看了源码 好像有用QDockWidget 但我真的不知道如何获得它的细节 当您手动移动辅助窗口
  • 如何在模型更改时停止ListView“跳跃”

    我需要做什么 我需要创建一个聊天窗口用一个ListView在 QML 中存储聊天消息 我设置listView positionViewAtEnd 以便跟踪最后的消息 我禁用positionViewAtEnd当我向上滚动时 我可以阅读过去的消

随机推荐

  • 2016去哪儿编程题:5-血型遗传检测

    题目描述 血型遗传对照表如下 父母血型 子女会出现的血型 子女不会出现的血型 O与O O A B AB A与O A O B AB A与A A O B AB A与B A B AB O A与AB A B AB O B与O B O A AB B与
  • shell 数组(字符串下标)

    现在游戏开的服务器越来越多了 每次用ssh操作都要写ip地址 很烦 也容易出错 所以要自己搞个服务器名到ip的映射 map anahost count 0 temp cat home linwencai sh HOST while read
  • ubuntu18.04合并pdf文件

    以前使用pdftk比较常见 但是pdftk的更新似乎没有跟上 改用pdfunite轻松解决 pdftk原来使用apt安装 现在改成用snap安装pdftk sudo snap install pdftk pdftk合并命令为 pdftk p
  • 洛谷 P1200 [USACO1.1]你的飞碟在这儿Your Ride Is Here

    题目链接 https www luogu com cn problem P1200 include
  • C++函数基础

    一 函数的定义和使用 1 函数的定义 类型说明符 函数名 含类型说明的形参表 语句序列 如 int GetSum int a int b return a b 2 形式参数 形式参数的作用是实现主函数与被调函数之间的联系 3 函数的返回值和
  • 【ubuntu】ubuntu实体机与windows互传文件(两台电脑)

    先记录一些命令 dpkg list 查看软件列表 sudo apt get purge remove 包名 purge是可选项 写上这个属性是将软件及其配置文件一并删除 如不需要删除配置文件 可执行sudo apt get remove 包
  • Python列表操作中extend和append的区别

    1 用法 append 用于在列表末尾添加新的对象 输入参数为对象 extend 用于在列表末尾追加另一个序列中的多个值 输入对象为元素队列 2 相同点 两个都是对列表即list进行的操作 具体句法可以写为 list1 append obj
  • 解决EXPLORER应用程序错误,桌面出不来

    打开运行 输入CMD 输入for 1 in windir system32 dll do regsvr32 exe s 1 意思是注册所有DLL组件 一般都能解决问题 转载于 https blog 51cto com feifei888 4
  • word文件doc、docx转pdf

    综合类管理系统不管是自研还是外包项目都会被客户或者产品经理要求 实现word导出 excel导出 pdf导出等功能 其实pdf导出呢 有很多种方式 我实现过的就有两种 接下来呢 就说说其中的一种 就是当你已经实现了word导出 或有明确的要
  • 粉丝文化:抖音广告短视频美妆营销中,男明星比女明星更带货?

    1996年 木村拓哉为佳丽宝拍摄了一支口红广告 这条广告轰动一时 代言的口红两个月就卖出了300万支 从此 男明星就成了美妆品牌的宠儿 众多美妆品牌开始启用男明星代言人 男明星为何有如此强力的带货潜力 美妆品牌如何在短视频时代占得先机 抖音
  • 阿里天池比赛——街景字符编码识别

    文章目录 前言 一 街景字符编码识别 1 目标 2 数据集 3 指标 总结 前言 之前参加阿里天池比赛 好久了 一直没有时间整理 现在临近毕业 趁论文外审期间 赶紧把东西整理了 5月底学校就要让我们滚蛋了 哭哭哭 大运会的牺牲品 一 街景字
  • 赛马游戏的java设计_赛马游戏源码

    0 Intro pos g setClip 10 10 HorseMidlet imgIntro 0 getWidth HorseMidlet i mgIntro 0 getHeight g drawImage HorseMidlet im
  • 面试题记1

    希望各位看客们能积极提供答案 1 125874和它的两倍251748 包含着同样的数字 只是顺序不同 找出最小的正整数x 使得2x 3x 4x 5x 和6x都包含有相同的数字 2 求100 各位数之和 3 是用从1到9所有数字 将其任意的连
  • Notion?Roam?OneNote? 不要再用这些垃圾做笔记啦

    双向链接 最近因为Roam Research 双向链接在笔记圈子里火了起来 Notion也在准备做了 那么双向链接是什么呢 我用我的我关于管道的一则笔记给大家讲明白 管道的实现 Linux里 管道实现的原理是 Shell进程先调用pipe创
  • 浅谈 qmake 之 shadow build

    shadow build shadow build 是什么东西 就是将源码路径和构建路径分开 也就是生成的makefile文件和其他产物都不放到源码路径 以此来保证源码路径的清洁 这不是qmake独创的东西 cmake中早就使用这个东西了
  • 性能测试_Day_10(负载测试-获得最大可接受用户并发数)

    目录 如何理解负载测试 如何实现负载测试 jpgc Standard Set插件安装 jpgc Standard Set使用方法 负载测试分析指标 获得最大可接受用户并发数 区间值 负载测试分析指标 获得最大可接受用户并发数 真实值 负载测
  • 阅读论文《Deep Bilateral Learning for Real-Time Image Enhancement》

    这是2017 siggraph的一篇论文 寒假boss让我看这篇论文我没怎么看懂 最近在公司实习 发现该论文的成果已经移到手机端上了 效果还非常不错 这里我重新温习了一下这篇论文 发现有许多可以借鉴的地方 是一篇非常不错的论文 这里重新叙述
  • 我碰到avs错误

    1 写好的avs脚本用播发器不能播放 并且报unexpected chatacter 错误 解决办法 1 尽管avs支持汉语文件路径 但是仍要确认标点符号是否为英文状态下 2 将AVS脚本用记事本打开 重新存为并把编码格式修改成ASNI格式
  • 数值计算方法python实现

    包括 泰勒级数展开 差分逼近微分 二分法求解 试位法求解 迭代法求根 牛顿法求根 正割法 贝尔斯托法多项式求跟 多项式回归 牛顿差商插值 拉格朗日插值法 三次样条插值法 二次样条插值法 高斯消元法 求解线性代数方程组 代码在我的github
  • 事件循环与线程 一

    初次读到这篇文章 译者感觉如沐春风 深刻体会到原文作者是花了很大功夫来写这篇文章的 文章深入浅出 相信仔细读完原文或下面译文的读者一定会有收获 由于原文很长 原文作者的行文思路是从事件循环逐渐延伸到线程使用的讨论 译者因时间受限 暂发表有关