Qt开发者关于QThread的咆哮——你们都用错了

我们(Qt用户)正广泛地使用IRC来进行交流。我在Freenode网站挂出了#qt标签,用于帮助大家解答问题。我经常看到的一个问题(这让我不厌其烦),是关于理解Qt的线程机制以及如何让他们写的相关代码正确工作。人们贴出他们的代码,或者用代码写的范例,而我则总是以这样的感触告终:
你们都用错了!

我觉得有件重要的事情得澄清一下,也许有点唐突了,然而,我不得不指出,下面的这个(假想中的)类是对面向对象原则的错误应用,同样也是对Qt的错误应用。

 
 
 
 
 

C++

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyThread : public QThread
{
public:
    MyThread()
    {
        moveToThread(this);
    }
 
    void run();
 
signals:
    void progress(int);
    void dataReady(QByteArray);
 
public slots:
    void doWork();
    void timeoutHandler();
};

我对这份代码最大的质疑在于 moveToThread(this);  我见过太多人这么使用,并且完全不明白它做了些什么。那么你会问,它究竟做了什么?moveToThread()函数通知Qt准备好事件处理程序,让扩展的信号(signal)和槽(slot)在指定线程的作用域中调用。QThread是线程的接口,所以我们是在告诉这个线程在“它内部”执行代码。我们也应该在线程运行之前做这些事。即使这份代码看起来可以运行,但它很混乱,并不是QThread设计中的用法(QThread中写的所有函数都应该在创建它的线程中调用,而不是QThread开启的线程)。

在我的印象中,moveToThread(this);  是因为人们在某些文章中看到并且使用而流传开来的。一次快速的网络搜索就能找到此类文章,所有这些文章中都有类似如下情形的段落:

  1. 继承QThread类
  2. 添加用来进行工作的信号和槽
  3. 测试代码,发现槽函数并没有在“正确的线程”中执行
  4. 谷歌一下,发现了moveToThread(this);  然后写上“看起来的确管用,所以我加上了这行代码”

我认为,这些都源于第一步。QThread是被设计来作为一个操作系统线程的接口和控制点,而不是用来写入你想在线程里执行的代码的地方。我们(面向对象程序员)编写子类,是因为我们想扩充或者特化基类中的功能。我唯一想到的继承QThread类的合理原因,是添加QThread中不包含的功能,比如,也许可以提供一个内存指针来作为线程的堆栈,或者可以添加实时的接口和支持。用于下载文件、查询数据库,或者做任何其他操作的代码都不应该被加入到QThread的子类中;它应该被封装在它自己的对象中。

通常,你可以简单地把类从继承QThread改为继承QObject,并且,也许得修改下类名。QThread类提供了start()信号,你可以将它连接到你需要的地方来进行初始化操作。为了让你的代码实际运行在新线程的作用域中,你需要实例化一个QThread对象,并且使用moveToThread()函数将你的对象分配给它。你同过moveToThread()来告诉Qt将你的代码运行在特定线程的作用域中,让线程接口和代码对象分离。如果需要的话,现在你可以将一个类的多个对象分配到一个线程中,或者将多个类的多个对象分配到一个线程。换句话说,将一个实例与一个线程绑定并不是必须的。


我已经听到了许多关于编写Qt多线程代码时过于复杂的抱怨。原始的QThread类是抽象类,所以必须进行继承。但到了Qt4.4不再如此,因为QThread::run()有了一个默认的实现。在之前,唯一使用QThread的方式就是继承。有了线程关联性的支持,和信号槽连接机制的扩展,我们有了一种更为便利地使用线程的方式。我们喜欢便利,我们想使用它。不幸的是,我太晚地意识到之前迫使人们继承QThread的做法让新的方式更难普及。

我也听到了一些抱怨,是关于没有同步更新范例程序和文档来向人们展示如何用最不令人头疼的方式便利地进行开发的。如今,我能引用的最佳的资源是我数年前写的一篇博客


免责声明:你所看到的上面的一切,当然都只是个人观点。我在这些类上面花费了很多精力,因此关于要如何使用和不要如何使用它们,我有着相当清晰的想法。


译者注:

最新的Qt帮助文档同时提供了建立QThread实例和继承QThread的两种多线程实现方式。根据文档描述和范例代码来看,若想在子线程中使用信号槽机制,应使用分别建立QThread和对象实例的方式;若只是单纯想用子线程运行阻塞式函数,则可继承QThread并重写QThread::run()函数。

由于继承QThread后,必须在QThread::run()函数中显示调用QThread::exec()来提供对消息循环机制的支持,而QThread::exec()本身会阻塞调用方线程,因此对于需要在子线程中使用信号槽机制的情况,并不推荐使用继承QThread的形式,否则程序编写会较为复杂。


扩展阅读:QObject 之 Thread Affinity

注:

    1. Thread Affinity:线程相关性
    2. “删除QThread对象前,确保线程内所有对象都没销毁”一句有误,应为“被销毁”,Qt文档中相关记录为“You must ensure that all objects created in a thread are deleted before you delete the QThread.”

Qt开发者关于QThread的咆哮——你们都用错了的更多相关文章

  1. Qt 线程基础(QThread、QtConcurrent等)

    [-] 使用线程 何时使用其他技术替代线程 应该使用 Qt 线程的哪种技术 Qt线程基础 QObject与线程 使用互斥量保护数据的完整 使用事件循环防止数据破坏 处理异步执行 昨晚看Qt的Manua ...

  2. Qt 线程基础(QThread、QtConcurrent等) 2

    使用线程 基本上有种使用线程的场合: 通过利用处理器的多个核使处理速度更快. 为保持GUI线程或其他高实时性线程的响应,将耗时的操作或阻塞的调用移到其他线程. 何时使用其他技术替代线程 开发人员使用线 ...

  3. Qt 线程基础(QThread、QtConcurrent、QThreadPool等)

      使用线程 基本上有种使用线程的场合: 通过利用处理器的多个核使处理速度更快. 为保持GUI线程或其他高实时性线程的响应,将耗时的操作或阻塞的调用移到其他线程. 何时使用其他技术替代线程 开发人员使 ...

  4. 【转】为什么我们都理解错了HTTP中GET与POST的区别

    GET和POST是HTTP请求的两种基本方法,要说它们的区别,接触过WEB开发的人都能说出一二. 最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数. 你可能自己 ...

  5. eclipse中的出现在打包一次后,后面新建的项目都出错了,出现support_v7下面出现红线及解决方法及为什么eclipse中项目继承ActionBarActivity解决方法一样

    第一次写博客,有什么问题或者想法的希望各位可以进行评论交流,望大家多多包涵! 遇到的问题是在新建的项目都出错了,出现support_v7下面出现红线及解决方法及为什么eclipse中项目继承Actio ...

  6. 【Qt开发】QThread 实用技巧、误区----但文档中没有提到

    本文主要内容: 在任务一中,用 四 种方式实现:点击界面按钮,开线程运行一段程序,结果显示在一个Label上.1. 用不正确的方式得到看似正确的结果2. 用Qt Manual 和 例子中使用的方法3. ...

  7. Qt——线程类QThread

    本文主要介绍Qt中线程类QThread的用法,参考(翻译+修改)了一篇文章:PyQt: Threading Basics Tutorial,虽然使用的是PyQt,但与C++中Qt的用法大同小异,不必太 ...

  8. QT下的QThread学习(一)

    参考文档如下: http://blog.csdn.net/styyzxjq2009/article/details/8204506 上面这篇文章的开头也也出了另外两篇文章,一并看看,可以看到他的解决思 ...

  9. 【Qt开发】QThread介绍

    回顾Qt之线程(QThread),里面讲解了如何使用线程,但还有很多人留言没有看明白,那么今天我们来一起瞅瞅关于QThread管理线程的那些事儿... 一.线程管理 1.线程启动 void start ...

随机推荐

  1. ubuntu nginx本地局域网布署sever_name设置

    如果没有设置好sever_name 在本地输入虚拟机的ip.只会看到nginx的helloworld(打招呼界面,不可能写helloworld)界面 重点在于nginx的布署文件要加上这么一条   来 ...

  2. python,dict的setdefault方法

    @dict的setdefault方法 先看看文档中的解释 setdefault(...)    D.setdefault(k[,d]) -> D.get(k,d), also set D[k]= ...

  3. Requests中文乱码解决方案

    分析: r = requests.get(“http://www.baidu.com“) **r.text返回的是Unicode型的数据. 使用r.content返回的是bytes型的数据. 也就是说 ...

  4. iOS UI的几种模式

    iOS UI的几种模式: 1.平凡模式(原生控件组合): 2.新闻模式: 3.播放器模式: 4.微博模式:

  5. swift 第一个IOS应用程序

    swift 出来也有一阵子了,一直没有时间来研究.简单的看了看.随手写几篇文章.特此声明:本博客纯属个人学习,有不足之处,属于正常,希望多多见谅. 第一个IOS应用程序开发 一.准备工作: (1)Ma ...

  6. BZOJ1079:[SCOI2008]着色方案(DP)

    Description 有n个木块排成一行,从左到右依次编号为1~n.你有k种颜色的油漆,其中第i种颜色的油漆足够涂ci个木块. 所有油漆刚好足够涂满所有木块,即c1+c2+...+ck=n.相邻两个 ...

  7. gluoncv voc_detection

    https://github.com/zhreshold/gluon-cv/commit/73b3986aaa2e0d0e6f3f428c12072e3a9d29905e gluoncv可能版本还没更 ...

  8. chromedriver linux windows各版本下载地址

    taobao镜像:https://www.baidu.com/link?url=gV12RWo7v_F-BDncFNKv_Rk9jF2nMix3Z7yMd84c2QvIB0LqcwxMxTPMUyb0 ...

  9. 使用 JLINK 的 RTT 功能 进行 调试打印数据

    jlink V9 时,在 SWD 接口 模式 时  ,要 接 SWO 这个引脚 ,否则导致 在 FreeRTOS的任务中不能使用,  正确的 接线方法 是  VCC,GND,SWDIO,SWCLK,S ...

  10. MySQL的笔记

    一.   SELECT tmp2.name,tmp2.browseNum FROM (SELECT tmp.`name`, COUNT(tmp.id) AS browseNum FROM(SELECT ...