The Eight Rules of Multithreaded Qt

发布于:2025-09-05 ⋅ 阅读:(26) ⋅ 点赞:(0)

The Eight Rules of Multithreaded Qt | KDAB

多线程Qt的8个规则


在 Qt 中多线程编程的注意事项及禁忌事项

The biggest dos and don'ts : 最重要的注意事项与禁忌事项

dos and don'ts : 规则或指南:一组在特定情况下应该遵循或不遵循的规则或指南。

10个评论 2020.1.7


approver : 批准者;承认者,赞成者

range : 涉及         from ... to ...

passionate : 热诚的,狂热的;热恋的,情意绵绵的;易怒的

specialist : 专家

conference : (大型、正式的)会议,研讨会;

hold a Bsc : 获得理学学士学位

理学学士(Bachelor of Science):一个学位,通常授予在科学、工程、数学等领域完成学业的大学毕业生。

on opensource : 关于开源

around italy : 在意大利境内


straightforward : 简单的,易懂的

be responsible for : 对……负责,是造成……原因:指对某事负有责任或是某事的原因。

wicked : 邪恶的,不道德的;恶作剧的,淘气的,调皮的;<非正式>很坏的,恶劣的;<非正式>很棒的,极好的;<非正式>令人不快的,令人厌恶的

nearly : 几乎,差不多;密切地,亲密地;即将,就要

reproduce : 复制,翻印;模拟;再制造,再现;繁殖,生殖

track down : 追踪、寻找:寻找某人或某物的行动。

bullet-proof : 防弹的                proof : adj.防……的,耐……的;试印的

a tall order : 难以完成的任务

a little : 一点儿,少量

look a little deeper into why that is : 再深入探究一下其中的原因吧


better than : 超过正常水平:用于描述某物在性能上超过正常水平。

internal : 

adj.内部的,体内的;内政的,国内的;本身的,本质的;内心的;<英>(大学生)本校生的

n.内部部件,内部特征;内脏

trouble-spots : 问题点

primitives : [计]基元(primitive 的复数);原始事物;基本体

tricky : 难对付的,棘手的;狡猾的,诡计多端的

inherent : 内在的,固有的;<法律>(权利,特权)固定属于(某人)的;(形容词)作定语和表语时意义相同的

And finally you need to understand how to use debugging tools with multiple threads to be able to find those tricky to reproduce issues that are inherent in multithreading bugs.


when it comes to : 就……而言:用于指明正在谈论的特定话题。

as well as : 和,以及,还有:用于连接两个或更多的事物,表示它们都是同等重要的或同样存在的。

Shoot your foot off : 开枪打中自己的脚

hone : 磨练,训练(尤指技艺);磨(刀、剑等);导向,朝向(hone in on);渴望;发牢骚

expertise : 专长,专门技能(知识);专家的意见

over the years : 多年来:在几年/一些年/多年期间。

pitfalls : 陷阱;诱惑(pitfall 的复数)

the first time : 第一次


event-driven design : 事件驱动设计

change x into y : 将 x 变为 y

wait for : 等待:等待某个特定事件的发生,或者等待某人或某物的到来。

better all : 更好一些

no x at all : 根本就没有 x 这个东西。

save : 救助,搭救;避免,减少;积攒,储蓄;节省(钱、时间或其他资源);保存,保留;收集;(为某人)保留;(计算机)保存(数据);使健康长寿,护佑;(足球等)救球,防止对方进球;(棒球)(替补投手) 保持(其他投手取得的领先地位);(基督教)拯救(某人),使摆脱罪孽

a huge amount of : 大量的:指非常多或数量巨大的事物。

otherwise : 

adv.否则,不然;除此以外,在其他方面;不同地,另外地;以其他方式,用别的方法

adj.不是那样的,另外情况下的

idle : 无事可做的;懒惰的;闲置的;空闲的;琐碎无聊的,毫无意义的;虚张声势的,唬人的;(钱)现金保存的,存于无息账户的

QThread::sleep() is also bad for timing since the amount of time it takes before control is returned is poorly constrained.

be bad for : 对……有害:指某种行为或物质对某人或某物有负面影响或危害

take : 携带,拿走;带去,引领;使达到,提升;拿,取;移走,拿开;偷走,误拿;取材于,收集;攻占,控制;选中,买下;订阅(报纸等);吃,服用;减去;记录,摘录;照相,摄影;量取,测定;就(座);以…...为例;接受,收取;接纳,接待(顾客、患者等);遭受,经受;忍受,容忍;(以某种方式)对待,处理;理解,考虑;误以为;赢得(比赛、竞赛等);产生(感情),持有(看法);采取(措施),采用(方法);做,拥有;采用(形式),就任(职位);花费,占用(时间); 需要,要求;使用;穿(特定尺码的鞋或衣物);容纳;授课;学习,选修(课程);参加(考试或测验);走(路线),乘坐(交通工具);跨过,跳过;踢,掷;举行投票,进行民意调查;成功,奏效;(语法)需带有(某种结构)

poorly : 贫穷地;贫乏地;不充分地

constrained : v.驱使;强迫(constrain 的过去式和过去分词)

adj.不自然的;强迫的;过于受约束的,拘泥的

Sleeping threads can also theoretically cause problems during application termination; foreground threads can prevent the application from terminating until they awake, while background threads may never reawaken, preventing clean finalization.

theoretically : 理论地,理论上

termination : 终止妊娠,人工流产;结束,终止;<美>解聘,解雇;<美>暗杀;词尾(尤指屈折变化或派生词的词尾);<古>结局

foreground : 前景;最显著的位置

prevent from : 防止,避免:采取措施以防止某事发生或某人做某事。

awake : adj.醒着的

v.(使)醒来,唤醒;唤起,激发起;意识到(awake to)

reawaken : 使想起,使回忆;再次唤醒

prevent : 阻止,阻碍;防止,预防;设置障碍

前台线程能够在它们苏醒之前阻止应用程序终止,而后台线程则可能永远无法重新苏醒,从而导致无法进行彻底的清理工作。


2. Never do GUI operations off the main thread

Qt's GUI operations are not thread safe, so non-main threads cannot safely perform any GUI operation. That means no widgets, QtQuick, QPixmap, or anything that touches the window manager.

“切勿在主线程之外执行GUI操作”:

Qt的GUI操作并非线程安全,因此非主线程无法安全地执行任何GUI操作。这意味着不能在任何非主线程中操作 widgets、QtQuick、QPixmap 或任何涉及窗口管理器的内容。

There are some exceptions: GUI functions that only manipulate data but don't touch the window manager can be called on secondary threads, things like QImage and QPainter. Be careful though, as classes like QBitmap or QPixmap actually are not safe. Check the documentation for each API: if you don't see a note at the top of the documentation saying that the function is reentrant, it's not safe to be called except from the main thread.

但有一些例外:某些仅操作数据而不涉及窗口管理器的GUI函数可以在辅助线程中调用,例如 QImage和 QPainter。不过仍需谨慎,因为像 QBitmap或 QPixmap这样的类实际上并不安全。务必查阅每个API的文档:如果你在文档顶部没有看到注明该函数是“可重入”(reentrant)的,那么除了主线程之外,在其他线程中调用它都是不安全的。

3. Don't block the main thread

Don't call any function that can block for an unspecified amount of time on the main thread (like QThread::wait()). Since these functions stop the main thread from running, all event processing halts and your UI freezes. If you wait long enough, the OS application manager will think your app is frozen and ask the user if they want to kill it – not cool. Both are recipes for an unfriendly app.

​不要阻塞主线程 (Don't block the main thread)​

切勿在主线程上调用任何可能阻塞不确定时间的函数(例如 QThread::wait())。因为这类函数会停止主线程的运行,导致所有事件处理中止,进而造成用户界面(UI)冻结。如果阻塞时间过长,操作系统应用程序管理器可能会认为你的应用程序无响应,并询问用户是否要强制终止它——这体验很差。阻塞主线程是导致应用程序响应迟钝的根源。

4. Always destroy QObjects on the thread that owns them

Qt isn't designed to allow you to destroy a QObject from any thread that doesn't own it. That means that before a QThread is destroyed, all QObjects that the thread owns need to be destroyed first. Failing to clean up properly can cause data integrity issues, like the ever popular memory leaks and/or crashes.

How do you ensure the correct thread is the one destroying your QObject? Either create it as an automatic variable inside the QThread's run() method, connect QThread::finished() to QObject::deleteLater(), or delay the destruction by moving the QObject to another thread with moveToThread(). Note that once you move a QObject away from the thread that owns it, you cannot touch it any more using that thread; you must use the new thread that owns the object.

始终在拥有 QObject 的线程中销毁它 (Always destroy QObjects on the thread that owns them)​

Qt 的设计并不允许你从任何非拥有它的线程销毁一个 QObject。这意味着,在销毁一个 QThread 之前,必须首先销毁该线程所拥有的所有 QObject。未能正确清理会导致数据完整性问题,例如常见的内存泄漏和/或程序崩溃。

如何确保由正确的线程销毁你的 QObject?你可以选择以下方法:在 QThread 的 run()方法内部将其创建为自动变量;将 QThread::finished()信号连接到 QObject::deleteLater()槽;或者通过 moveToThread()将 QObject 移动到另一个线程来延迟其销毁。请注意,一旦你将 QObject 移离了拥有它的原始线程,便不能再使用该原始线程来操作此对象;你必须使用现在拥有该对象的新线程。

5. Don't trust your intuition when it comes to synchronization

A very common design pattern is that one thread signals its status to a monitoring thread, usually by writing to a boolean state variable that the monitoring thread can poll. With a data structure of a single word, only one thread writing to it, and only one thread reading it, it seems like this situation wouldn't actually require concurrency protection since the read is guaranteed to happen eventually, right? Actually, even this simple case isn't safe.

The C++ standard says that thread synchronization is mandatory and anything outside of the specification can result in undefined behaviour. If you're not synchronizing – even in a "simple" case – you're asking for trouble. In fact, some serious bugs have been found in the Linux kernel, in situations exactly as described here. The best thing to do is to not overthink what is safe or not – if there are concurrent accesses to the same data from multiple threads, no matter how unlikely they are to cause problems, protect them with appropriate synchronization mechanisms.

在同步问题上不要轻信直觉 (Don't trust your intuition when it comes to synchronization)​

一个非常常见的设计模式是:一个线程通过写入一个布尔状态变量来向其监视线程通知状态,监视线程可以轮询该变量。对于一个单字(single word)的数据结构,只有一个线程写入,也只有一个线程读取,这种情况似乎不需要并发保护,因为读取操作最终肯定会发生,对吧?实际上,即使这么简单的情况也是不安全的。

C++ 标准明确指出线程同步是强制性的,规范之外的任何行为都可能导致未定义行为。如果你不同步——即使在“简单”的情况下——你就是在自找麻烦。事实上,在 Linux 内核中就发现过一些严重的 bug,其情况 exactly 如同这里所描述的那样。最好的做法是不要过度纠结什么安全什么不安全——​​只要存在多个线程并发访问相同数据的情况,无论看起来多么不可能引起问题,都要使用适当的同步机制(如互斥锁 Mutex )来保护它们​​。

6. Act as if QObject is non-reentrant

A reentrant function means that as long as different threads are dealing with different data, it can be safely used without synchronization. The Qt documentation indicates that QObject is reentrant, but there are many caveats to this re-entrancy:

  • Event-based classes aren't reentrant (timers, sockets, etc.)
  • Event dispatching for a given QObject happens in the thread it has affinity with; this can cause races within Qt if you touch the object from another thread
  • All QObjects in the same parent/child tree must have the same thread affinity
  • You must delete all QObjects owned by a thread before deleting the QThread
  • You can only call moveToThread() on an object from the thread the object has affinity with

To avoid all of these special cases, it's usually easier to just act as if QObject isn't reentrant. In practice, this means that you should only touch a QObject on the thread that owns it. This will keep you out of all the non-obvious corner cases that can cause trouble.

姑且认为 QObject 是不可重入的 (Act as if QObject is non-reentrant)​

可重入(Reentrant)函数意味着,只要不同的线程处理不同的数据,就可以安全地使用它而无需同步。Qt 文档指出 QObject 是可重入的,但这种可重入性有许多注意事项:•

基于事件的类(计时器、套接字等)不是可重入的。

给定 QObject 的事件分派是在其所属亲和线程(thread affinity)中进行的;如果你从另一个线程操作该对象,可能会在 Qt 内部引发竞争条件。

同一父/子树中的所有 QObject 必须具有相同的线程亲和性。

在删除 QThread 之前,必须删除该线程拥有的所有 QObject。

你只能从对象具有亲和性的线程中对它调用 moveToThread()

为了避免所有这些特殊情况,通常更简单的做法是​​姑且认为 QObject 是不可重入的​​。在实践中,这意味着​​你应该只在线程亲和性所属的线程中操作 QObject​​。这将帮助你避开所有可能引发麻烦的非显而易见的极端情况。

7. Avoid adding slots to QThread

Because QThread objects have affinity to the original thread that created them and (perhaps unintuitively) do not have affinity to themselves, this causes issues when trying to use signals and slots on a non-main thread. Although a design where a non-main thread uses a slot can be done, since it needs to side-step a lot of non-obvious gotchas our recommendation is that you just avoid this design.

If you don't need to override QThread:run(), then don't subclass QThread at all; just create an instance of it and you'll avoid problems with slots (see links at the end of this blog post for my talk for how to do this with workers).

避免向 QThread 添加槽函数 (Avoid adding slots to QThread)​

因为 QThread 对象亲和于创建它们的原始线程,并且(可能反直觉地)并不亲和于它们自身所在的线程,这导致在非主线程上尝试使用信号和槽时会出现问题。虽然这种非主线程使用槽函数的设计可以实现,但由于它需要规避许多非显而易见的陷阱,​​我们的建议是你应避免这种设计​​。

如果你不需要重写 QThread::run(),那么就根本不要子类化 QThread;只需创建它的一个实例,这样就可以避免与槽函数相关的问题(有关如何使用工作对象(workers)来实现这一点的更多信息,可以参阅本文末尾提到的演讲资料)。

8. Use standard library threads if it's more natural

Finally, both the C++ standard library as well as other third party libraries have a wide array of threading classes that aren't part of Qt – parallel algorithms, coroutines, latches, barriers, atomic smart pointers, continuations, executors, concurrent queues, distributed counters, and the like.

Qt's multi-threading capabilities are still better in some cases: for example, Qt has thread pools while the C++ standard still does not. The good news is that the C++ classes are all compatible with Qt and can be freely incorporated into your code. In fact, unless a thread manipulates QObjects and you must use Qt threads, either C++ or Qt threading classes can be used depending on what you prefer.

如果更自然,使用标准库线程 (Use standard library threads if it's more natural)​

最后,C++ 标准库以及其他第三方库提供了大量不属于 Qt 的线程类——并行算法、协程、latch、barrier、原子智能指针、continuation、executor、并发队列、分布式计数器等等。

Qt 的多线程能力在某些情况下仍然更胜一筹:例如,Qt 拥有线程池,而 C++ 标准库至今还没有。好消息是 ​​C++ 的线程类都与 Qt 兼容​​,可以自由地融入你的代码中。事实上,​​除非一个线程需要操作 QObject 因而必须使用 Qt 线程​​,否则根据你的偏好,使用 C++ 标准库线程或 Qt 线程类都是可以的。

If you liked this short summary, you may want to watch my full QThread talk given at QtCon or see my presentation slides on this topic. These delve much deeper into the reasons behind these rules, as well as providing code samples for the dos and don'ts.

如果你喜欢这个简短的总结,你可能想观看我在 QtCon 上的完整 QThread 演讲或查看有关此主题的演示文稿幻灯片。这些资料更深入地探讨了这些规则背后的原因,并提供了关于最佳实践和禁忌的代码示例。

关于KDAB

KDAB集团是全球知名的软件咨询、开发和培训服务提供商,专注于嵌入式设备与复杂的跨平台桌面应用程序领域。作为二十余年来Qt、C++及3D技术领域的权威专家,KDAB更具备涵盖Linux、Rust及现代UI框架等全技术栈的深度专业知识。我们拥有100余名来自20个国家的员工,在瑞典、德国、美国、法国和英国设有办事处,为全球客户提供专业技术服务。