QThrea的创建

Qt开启多线程,主要用到Qt自身提供的QThread类。继承 QThread ,然后重新改写虚函数run()。当要开启新线程时,只需要实例该类,然后调用函数start(),就可以开启一条多线程。这是相当直观和易于使用的。但是官方文档中我们找寻到另外一种创建和使用多线程的方法,继承一个QObject类,然后利用moveToThread()函数开启一个线程槽函数,将要花费大量时间计算的代码放入该线程槽函数中。我们一起来看看两种创建方式。

继承Qthread

基础使用

这是书籍和文档都会使用的方法。其使用方式有两种。

  • 不使用事件循环
    1. 子类化 QThread。
    2. 重载 run 函数,run函数内有一个 while 或 for 的死循环。
    3. 设置一个标记为来控制死循环的退出。
  • 使用事件循环

a. 子类化 QThread。
b. 重载 run 使其调用 QThread::exec() 。
c. 使用线程时,实例化新建的类对象。调用新建对象的start()函数
d.结束束时调用新建类的Wait()函数,等待线程退出,释放资源
由于定义了信号和槽函数,由于槽函数并不会在新开的 thread 运行,很多人为了解决这个问题在构造函数中调用 moveToThread(this)。所有Qt的开发人员Bradley T. Hughes 指出,这样编码是错误的。并给出说明:

QThread 应该被看做是操作系统线程的接口或控制点,而不应该包含需要在新线程中运行的代码。需要运行的代码应该放到一个QObject的子类中,然后将该子类的对象moveToThread到新线程中。

第一种使用方法在C++中也很常见,使用方法也很简单,给出简单的第二种实现。
代码如下:

  1. #include <QCoreApplication>
  2. #include<QThread>
  3. #include<QDebug>
  4. class myThread :public QThread
  5. {
  6. protected:
  7. void run()
  8. {
  9. qDebug()<< "myThread curID = "<<currentThreadId();
  10. QThread::exec();
  11. }
  12. };
  13. int main(int argc, char *argv[])
  14. {
  15. QCoreApplication a(argc, argv);
  16. myThread thread;
  17. thread.start();
  18. qDebug()<<"main curID = "<<QThread::currentThreadId();
  19. thread.wait();
  20. return a.exec();
  21. }

输出结果:

main curID = 0xfdfc myThread curID = 0xebb4

在使用继承方式实现线程时,需要主意一下几点:

  • 必须在创建QThread对象之前创建 QApplication (或QCoreApplication)对象。
  • QCoreApplication::exec() 必须只能从主线程调用,不能从QThread线程调用。

多说一点

其实一直有一个问题,我们还没有明确的进行说明,start()以后,除了run()函数在子线程中执行,其他的的函数呢?槽函数在哪执行呢? 是在次线程还是主线程中运行?其实这都和connect()函数有关,后面会说到
这里需要说明一点,正是因为继承使用槽函数时,存在一定的问题,所有推荐使用movetoThread().

movetoThread

很多人都推荐使用的方法,这种方法,解决了槽函数在新建线程执行的问题。其实,这个方法很简单,定义一个普通的QObject派生类,然后将其对象使用movetothread()函数移动到到QThread中。使用信号和槽时根本不用考虑多线程的存在。也不用使用QMutex来进行同步,Qt的事件循环会自己自动处理好这个。使用步骤为:
a.创建自己的类,继承自QObject。类中实现耗时操作。
b.主线程中创建类实例和Qthread实例。
c.使用创建类的movetothread(thread),移动至线程实例。
d.启动线程。
这样的例子很多,文档中也提供了一个线程池的例子,代码如下:

  1. #ifndef RUNNABLE_H
  2. #define RUNNABLE_H
  3. #include <QObject>
  4. #include <qthread.h>
  5. #include <QRunnable>
  6. #include <qdebug.h>
  7. //具体工作类
  8. class RunWork:public QObject{
  9. Q_OBJECT
  10. public:
  11. RunWork(){
  12. }
  13. void doWork(){
  14. qDebug()<<"RunWork--void doWork(){--"
  15. <<QThread::currentThread();
  16. emit sigSendMsg(QString());
  17. }
  18. ~RunWork(){
  19. qDebug()<<"RunWork--~RunWork(){--"
  20. <<QThread::currentThread();
  21. }
  22. signals:
  23. void sigSendMsg(const QString &s);
  24. public slots:
  25. void slotRevMsg(const QString&s){
  26. qDebug()<<"Work--"
  27. <<"void slotRevMsg(const QString&s){--"
  28. <<QThread::currentThread();
  29. }
  30. };
  31. //线程管理类
  32. class Runnable:public QRunnable
  33. {
  34. public:
  35. //将工作类作为参数传递到runnable类中
  36. Runnable(RunWork*w=NULL):m_work(w){}
  37. void run(){
  38. qDebug()<<"Runnable--void run(){--"
  39. <<QThread::currentThread();
  40. //在run函数中调用的函数才属于子线程
  41. m_work->doWork();
  42. }
  43. ~Runnable(){
  44. qDebug()<<"~Runnable(){--"
  45. <<QThread::currentThread();
  46. if(m_work)
  47. m_work->deleteLater();
  48. }
  49. private:
  50. RunWork *m_work;
  51. };
  52. //工作管理通信类
  53. class RunnableController:public QObject{
  54. Q_OBJECT
  55. public:
  56. RunnableController(){}
  57. signals:
  58. void sigSendMsg(const QString&s);
  59. public slots:
  60. void slotRevMsg(const QString&s){
  61. qDebug()<<"RunnableController::"
  62. <<"void slotRevMsg(const QString&s){--"
  63. <<QThread::currentThread();
  64. }
  65. };
  66. #endif // RUNNABLE_H

main函数如下:

  1. int main(int argc, char *argv[])
  2. {
  3. QApplication a(argc, argv);
  4. //QRunnable
  5. #if 1
  6. qDebug()<<QThread::idealThreadCount()
  7. <<QThread::currentThread();
  8. RunnableController *controller=new RunnableController;
  9. RunWork *work=new RunWork;
  10. //工作类作为构造参数传递到runnable类中
  11. Runnable *run=new Runnable(work);
  12. //将工作类与管理类信号槽绑定
  13. QObject::connect(controller,&RunnableController::sigSendMsg,
  14. work,&RunWork::slotRevMsg);
  15. QObject::connect(work,&RunWork::sigSendMsg,
  16. controller,&RunnableController::slotRevMsg);
  17. //启动线程
  18. QThreadPool::globalInstance()->start(run);
  19. // emit controller->sigSendMsg(QString("mian"));
  20. #endif
  21. return a.exec();
  22. }

输出结果:

  1. 2 QThread(0x3e4ab8)
  2. Runnable--void run(){-- QThread(0x3efcb0, name = "Thread (pooled)")
  3. RunWork--void doWork(){-- QThread(0x3efcb0, name = "Thread (pooled)")
  4. ~Runnable(){-- QThread(0x3efcb0, name = "Thread (pooled)")
  5. RunnableController:: void slotRevMsg(const QString&s){-- QThread(0x3e4ab8)
  6. RunWork--~RunWork(){-- QThread(0x3e4ab8)

代码详情索命,请跳转至:QThreadPool\QRunnable使用例子

线程安全

涉及信号槽,我们就躲不过 connect 函数,只是这个函数大家太熟悉。我不好意思再用一堆废话来描述它,但不说又不行,只看它的最后一个参数吧(为了简单起见,只看它最常用的3个值)

  • 自动连接(Auto Connection)
    • 这是默认设置
    • 如果信号在接收者所依附的线程内发射,则等同于直接连接
    • 如果发射信号的线程和接受者所依附的线程不同,则等同于队列连接
    • 也就是这说,只存在下面两种情况
  • 直接连接(Direct Connection)
    • 当信号发射时,槽函数将直接被调用。
    • 无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行。
  • 队列连接(Queued Connection)

    • 当控制权回到接受者所依附线程的事件循环时,槽函数被调用。
    • 槽函数在接收者所依附线程执行。

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

  • 队列连接告诉我们:槽函数在接受者所依附线程执行。即 slot 将在主线程执行

  • 直接连接告诉我们:槽函数在发送信号的线程执行。但是信号发送的位置是不定的,主线程、子线程均可。
  • 自动连接告诉我们:二者不同,等同于队列连接。即 slot 在主线程执行。

所以我们可以知道,QThread是作为一个线程入口点,来管理线程的,它依附于主线程,来管理新线程。所以一般来说,槽函数会在他所依赖的线程执行,即主线程。而如果想在新线程中执行槽函数,可以做以下两个操作:

  • 信号和发送都在新线程。
  • 使用movetothread(this)。

    正确结束

    我们应该采取合理的措施来优雅地结束线程,一般思路:
  1. 发起线程退出操作,调用quit()或exit()。
  2. 等待线程完全停止,删除创建在堆上的对象。
  3. 适当的使用wait()(用于等待线程的退出)和合理的算法。