QT多线程的设计(imxiangzi总结)

来源:互联网 发布:淘宝买东西后怎么评价 编辑:程序博客网 时间:2024/06/14 04:50

1. 不使用事件循环。这是官方的 Manual 、example 以及相关书籍中都介绍的一种的方法。

 

a. 子类化 QThread

b. 重载 run 函数,run函数内有一个 while 或 for 的死循环

c. 设置一个标记为来控制死循环的退出。


  1. void run()
  2. {  
  3.   
  4.     while(bRun)//如果需要退出线程就将bRun设置为false.  
  5.     {  
  6.         qDebug()<<"run thread ID = "<<QThread::currentThreadId();  
  7.         QThread::usleep(0);//usleep的值设置为0一样会占满cpu,但是根据老大测试,并不会使程序变卡。你也可以设置为10.  
  8.     }  
  9.     //  this->exec();//没加这个的话线程就会结束,并发出Finsh()信号  
  10. }  



如果使用这一方法,QThread::quit()没有效果。因为这个线程根本就不需要事件循环。这种情况想退出,将bRun设置为false或者直接使用QT很不推荐的terminate().

[如果想要通过exit和quit正常退出函数,则需要在run函数中添加exec函数]


2. 使用事件循环。(博客 you are-doing-it-wrong 批驳的就是这种情况下的 一种用法。)

a. 子类化 Thread,[有时候在构造函数中添加moveToThread(this)]

b. 重载 run 使其调用 QThread::exec() 

c. 并为该类定义信号和槽,


这样一来,由于槽函数并不会在新开的 thread 运行,很多人为了解决这个问题在构造函数中调用 moveToThread(this)
而争论和不解正是这样的一条语句造成的。
Bradley T. Hughes 给出说明是: QThread 应该被看做是操作系统线程的接口或控制点,而不应该包含需要在新线程中运行的代码。需要运行的代码应该放到一个QObject的子类中,然后将该子类的对象moveToThread到新线程中。


这是 Qt Manual 和 例子中普遍采用的方法。 但由于manual没说槽函数是在主线程执行的,所以不少人都认为它应该是在次线程执行了。

  • 定义一个 Dummy 类,用来发信号
  • 定义一个 Thread 类,用来接收信号
    • 重载 run 函数,目的是打印 threadid
/*!* \file main.cpp** Copyright (C) 2010, dbzhang800* All rights reserved.**/#include <QtCore/QCoreApplication> #include <QtCore/QObject> #include <QtCore/QThread> #include <QtCore/QDebug>  class Dummy:public QObject {     Q_OBJECT public:     Dummy(){} public slots:     void emitsig()     {         emit sig();     } signals:     void sig(); };  class Thread:public QThread {     Q_OBJECT public:     Thread(QObject* parent=0):QThread(parent)     {         //moveToThread(this);     } public slots:     void slot_main()     {         qDebug()<<"from thread slot_main:" <<currentThreadId();     } protected:     void run()     {         qDebug()<<"thread thread:"<<currentThreadId();         exec();     } }; #include "main.moc" int main(int argc, char *argv[]) {      QCoreApplication a(argc, argv);     qDebug()<<"main thread:"<<QThread::currentThreadId();     Thread thread;     Dummy dummy;     QObject::connect(&dummy, SIGNAL(sig()), &thread, SLOT(slot_main()));     thread.start();     dummy.emitsig();     return a.exec(); }

然后看到结果(具体值每次都变,但结论不变)

main thread: 0x1a40 from thread slot_main: 0x1a40 thread thread: 0x1a48

看到了吧,槽函数的线程和主线程是一样的!

如果你看过Qt自带的例子,你会发现 QThread 中 slot 和 run 函数共同操作的对象,都会用QMutex锁住。为什么?

因为slot和run处于不同线程,需要线程间的同步!

如果想让槽函数slot在次线程运行(比如它执行耗时的操作,会让主线程死掉),怎么解决呢?

  • 注意:dummy信号是在主线程发射的, 接收者 thread 也在主线程中。
  • 参考我们前面的结论,很容易想到:
    • 将 thread 依附的线程改为次线程不就行了?
    • 这也是代码中注释掉的 moveToThread(this)所做的,去掉注释,你会发现slot在次线程中运行

main thread: 0x13c0 thread thread: 0x1de0 from thread slot_main: 0x1de0

这可以工作,但这是 Bradley T. Hughes 强烈批判的用法。推荐的方法后面会给出。




方法(3): 
在Qt4.3(包括)之前,run 是虚函数,必须子类化QThread来实现run函数。

而从Qt4.4开始,qthreads-no-longer-abstract ,run 默认调用 QThread::exec() 。

这样一来不需要子类化 QThread 了,只需要子类化一个 QObject 就够了,这正是被 Bradley T. Hughes推荐的方法。

方法如下

QThread thread; Object obj; Dummy dummy; obj.moveToThread(&thread); QObject::connect(&dummy, SIGNAL(sig()), &obj, SLOT(slot())); thread.start();

具体的例子:

/*!* \file main.cpp** Copyright (C) 2010, dbzhang800* All rights reserved.**/#include <QtCore/QCoreApplication> #include <QtCore/QObject> #include <QtCore/QThread> #include <QtCore/QDebug>  class Dummy:public QObject {     Q_OBJECT public:     Dummy(QObject* parent=0):QObject(parent)     {} public slots:     void emitsig()     {         emit sig();     } signals:     void sig(); };  class Object:public QObject {     Q_OBJECT public:     Object(){} public slots:     void slot()     {         qDebug()<<"from thread slot:" <<QThread::currentThreadId();     } };  #include "main.moc"  int main(int argc, char *argv[]) {     QCoreApplication a(argc, argv);     qDebug()<<"main thread:"<<QThread::currentThreadId();     QThread thread;     Object obj;     Dummy dummy;     obj.moveToThread(&thread);     QObject::connect(&dummy, SIGNAL(sig()), &obj, SLOT(slot()));     thread.start();     dummy.emitsig();     return a.exec(); }

结果:恩,slot确实不在主线程中运行(这么简单不值得欢呼么?)

main thread: 0x1a5c from thread slot: 0x186c

==========================================

总结:

方法1和方法3是推荐使用的,

当有长时间的处理任务的时候,使用方法1,

当使用到信号和操的时候,使用方法3


==========================================

原因分析:

1. 

现在我们来分析下connect()的五个参数。

有六种参数:

1
2
3
4
5
6
Qt::AutoConnection
Qt::DirectConnection
Qt::QueuedConnection
Qt::BlockingQueuedConnection
Qt::UniqueConnection
Qt::AutoCompatConnection

这里面一共有六种方式。

前两种比较相似,都是同一线程之间连接的方式,不同的是Qt::AutoConnection是系统默认的连接方式。这种方式连接的时候,槽不是马上被执行的,而是进入一个消息队列,待到何时执行就不是我们可以知道的了,当信号和槽不是同个线程,会使用第三种QT::QueueConnection的链接方式。如果信号和槽是同个线程,调用第二种Qt::DirectConnection链接方式。

第二种Qt::DirectConnection是直接连接,也就是只要信号发出直接就到槽去执行,无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行,一旦使用这种连接,槽将会不在线程执行!

第三种Qt::QueuedConnection和第四种Qt::BlockingQueuedConnection是相似的,都是可以在不同进程之间进行连接的,不同的是,这里第三种是在对象的当前线程中执行,并且是按照队列顺序执行。当当前线程停止,就会等待下一次启动线程时再按队列顺序执行  ,等待QApplication::exec()或者线程的QThread::exec()才执行相应的槽,就是说:当控制权回到接受者所依附线程的事件循环时,槽函数被调用,而且槽函数在接收者所依附线程执行,使用这种连接,槽会在线程执行

第四种Qt::BlockingQueuedConnection是(必须信号和曹在不同线程中,否则直接产生死锁)这个是完全同步队列只有槽线程执行完才会返回,否则发送线程也会等待,相当于是不同的线程可以同步起来执行。

第五种Qt::UniqueConnection跟默认工作方式相同,只是不能重复连接相同的信号和槽;因为如果重复链接就会导致一个信号发出,对应槽函数就会执行多次。

第六种Qt::AutoCompatConnection是为了连接QT4 到QT3的信号槽机制兼容方式,工作方式跟Qt::AutoConnection一样。显然这里我们应该选择第三种方式,我们不希望子线程没结束主线程还要等,我们只是希望利用这个空闲时间去干别的事情,当子线程执行完了,只要发消息给主线程就行了,到时候主线程会去响应。


两个概念:发送信号的线程 和 接收者所依附的线程。而 slot 函数属于我们在main中创建的对象 thread,即thread依附于主线程

  • 队列连接告诉我们:槽函数在接受者所依附线程执行。即 slot 将在主线程执行
  • 直接连接告诉我们:槽函数在发送信号的线程执行。信号在那个线程发送呢??不定!
  • 自动连接告诉我们:二者不同,等同于队列连接。即 slot 在主线程执行

  • QThread 是用来管理线程的,它所依附的线程和它管理的线程并不是同一个东西
  • QThread 所依附的线程,就是执行 QThread t(0) 或 QThread * t=new QThread(0) 的线程。也就是咱们这儿的主线程
  • QThread 管理的线程,就是 run 启动的线程。也就是次线程
  • 因为QThread的对象依附在主线程中,所以他的slot函数会在主线程中执行,而不是次线程。除非:
    • QThread 对象依附到次线程中(通过movetoThread)
    • slot 和信号是直接连接,且信号在次线程中发射


参考:

在Qt使用moveToThread() qt的线程

http://blog.csdn.net/zhangbinsijifeng/article/details/47783599


QThread使用——关于run和movetoThread的区别

http://blog.csdn.net/imxiangzi/article/details/77482797