QT高级编程技巧(二)-- 编写多线程和并发应用

学习QT多线程编程之前,有必要先熟悉事件循环的概念。先看一个单线程界面程序的主函数代码:

int main(int argc, char* argv[])
{
  QApplication app(argc,  argv);

  // 构造主窗口对象并显示
  MainWindow w;
  w.show();

  // 进入事件循环
  return app.exec();
}

在程序初始化完成后,主线程进入main()函数开始执行应用代码。一般地,我们在主线程上构建界面对象,然后进入事件循环以处理控件绘制、用户输入、系统输出等消息。这就是我们通常说的事件驱动模型。

主线程承担着用户交互的重任,当在主线程上运行费时的代码时,就会影响用户的正常操作。所以我们常把一些费时费力的计算工作移出主线程,开辟新的线程来运行之。

QThread是QT中用于线程管理的类,调用一个QThread对象的start()方法时,会创建一个新的线程并执行它的run()方法。默认地,run()会调用exec()方法进入自己的消息循环中。如下图所示:

QT高级编程技巧(二)-- 编写多线程和并发应用

上图中有主线程、工作线程都是执行事件循环,并且注意到主线程内部含有thr、w、objs这些QObject对象(这些对象都是在主线程上创建的)。主线程的事件循环负责检测这些对象是否有消息要处理,有的话则调用对象的slot方法。可以使用QObject::moveToThread方法将某个对象移到其他线程中,譬如:

class Worker : public QObject {
    Q_OBJECT
    …
}

void someFunc()
{
   QThread thr = new QThread;
   Worker worker = new Worker;
   worker->moveToThread(thr);
   thr->start();
  … }

如果在主线程上调用someFunc(),则workerThread和worker在创建后都是关联在主线程上,当调用worker->moveToThread()后,worker对象关联到了新的线程中,如图所示:

QT高级编程技巧(二)-- 编写多线程和并发应用

假定我们在MainWindow上声明了一个worksSignal()消息,在Worker对象上声明和定义了handleWorks()的槽,将worksSignal和handleWorks连接起来的方式有:

1. Qt::AutoConnection - (默认)如果消息对象和槽对象关联在同一线程下,则使用Qt::DirectConnection方式;否则的话,像MainWindow和Worker两个关联在不同线程的对象,将采用Qt::QueuedConnection的方式。

2. QT::DirectConnection - 发送消息的时候将直接调用槽对象的槽方法。注意这里的槽方法是在发送消息的线程上执行的,如果该槽方法是非线程安全的话会有问题的。

3. Qt::QueuedConnection - 发送线程在发送消息后将继续执行,槽对象关联的线程在事件循环时会检测到该消息,并调用相应的槽方法。

4. Qt::BlockingQueuedConnection - 在主线程发送worksSignal消息后,将阻塞直到在工作线程检测到该消息并运行worker->handleWorks()后恢复。

5. Qt::UniqueConnection - 可以和上面4个方式联并(或操作),提示该连接是独一的。提示不能有相同的连接(消息对象和槽对象,消息和槽都相同)出现。

这里特别提醒读者,一般地我们不建议将QThread对象moveToThread到它运行的线程上。原因是QThread是设计成一个管理线程的类,我们不应该在工作线程上管理工作线程,对吧。关于更多的技术细节,我不想多讲了,因为本系列的博文旨在共享经验技巧,而非翻译一些文档。

在项目中,我都是通过继承QThread类实现后台进程的,通过重写run()函数填入线程需要运行的任务。上一篇博文中,我通过在QThread子类上嵌入InThreadObject对象快速实现线程通信的功能,请回顾QT高级编程技巧(一)-- 编写高效的signal & slot通信代码。还有下面一个技巧实现工作线程上的timer事件处理:

void WorkerThread::run()
{
  QTimer tmr;      // 关联在本线程上的QObject对象
  connect(&tmr, &QTimer::timeout, [=](){
    doSomething();
  });
  tmr.start(500);  // 500毫秒计时
  exec();          // 进入事件循环
}

关于多线线程的编程谈论到此,望能起抛砖引玉的效果。在实际的项目,可以参考下面的文档设计多线程或并发的应用:

* QtConcurrent: http://doc.qt.io/qt-5/qtconcurrent.html

* Thread Support in Qt: http://doc.qt.io/qt-5/threads.html

 

本文链接:http://www.cnblogs.com/wenris/p/4450643.html

上一篇:Nginx


下一篇:Java countDownLatch 线程辅助类