简析奇妙的Qt多线程,其同时兼备同步和异步

简析Qt多线程

  • 序言
  • 一、两种线程实现方法
  • 二、继承QObject的多线程方法的原理解析
    • 1、moveToThread原理
    • 2、线程的事件循环
  • 三、同步异步的实现
  • 四、跨平台特性的多线程

序言

老实说我不大想写这个的,因为麻烦,但是嘛,我记得有一次面试的时候,面试官听到我说我用的Qt QObject的方法作为多线程时是异步之后,开始明显怼我该怎么实现同步,我那时的回答是,Qt的多线程既可同步也可异步,这面试官不信,我那时对这个Qt多线程了解不多,只知道怎么用,但是不知道为什么,又没答上来,这给我气的呀,所以希望看到我的博客的Qt开发者能够对这方面了解多一点。

一、两种线程实现方法

相比很多人都知道Qt有两种最多人熟知的多线程方法:
第一种是大家常见的,继承QThread类,并重写run,这一种符合常规的多线程使用,加锁同步,正常异步,使用只需调用实例的start():

class WorkerThread : public QThread
 {
     Q_OBJECT
     void run() override {
         QString result;
         /* ... here is the expensive or blocking operation ... */
         emit resultReady(result);
     }
 };

第二种是官方主推的继承QObject,也是我这篇文章的主要解释内容:

class Worker : public QObject
 {
     Q_OBJECT

 public slots:
     void doWork(const QString &parameter) {
         QString result;
         /* ... here is the expensive or blocking operation ... */
         emit resultReady(result);
     }

 signals:
     void resultReady(const QString &result);
 };

 class Controller : public QObject
 {
     Q_OBJECT
     QThread workerThread;
 public:
     Controller() {
         Worker *worker = new Worker;
         worker->moveToThread(&workerThread);
         connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
         connect(this, &Controller::operate, worker, &Worker::doWork);
         connect(worker, &Worker::resultReady, this, &Controller::handleResults);
         workerThread.start();
     }
     ~Controller() {
         workerThread.quit();
         workerThread.wait();
     }
 public slots:
     void handleResults(const QString &);
 signals:
     void operate(const QString &);
 };

以上代码均取自Qt助手的官方文档
.

二、继承QObject的多线程方法的原理解析

大家乍一看代码可能有点懵,看不懂,我慢慢分析给你看。

其使用的重点在于这里

Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::operate, worker, &Worker::doWork);
connect(worker, &Worker::resultReady, this, &Controller::handleResults);
workerThread.start();

1、moveToThread原理

moveToThread是核心重点,将你实际要使用的线程即Worker类放到workerThread线程里。

当workerThread运行线程的时候,Worker就会在workerThread里其中一个“运行节点”运行。

这怎么理解呢?看下图,Worker就是你写的实际运行类,workerThread实例就是所代表的QThread

QObject
QThread
实际运行类
节点3
节点1
节点2

有些人可能不明白,QThread怎么就是一个循环了呢?这就是重点了,

官方解释:QThreads begin executing in run(). By default, run() starts the event loop by calling exec() and runs a Qt event loop inside the thread.

有道译文:QThreads在run()中开始执行。默认情况下,run()通过调用exec()启动事件循环,并在线程中运行Qt事件循环。

这说明了什么呢?想必很多人已经恍然大悟,或者懵懂。

不打哑谜,这意味着,通过这个方法开启的线程是通过事件循环的方法进行的线程运行!

2、线程的事件循环

事件循环,打个简单的例子:你正常使用的Qt主线程,运行方式就是通过事件循环来进行的。

事件循环:在没有事件时等待事件,出现事件时进行处理,处理完继续等待下一个事件。		——溪渣渣_梁世华

什么是事件?事件就是任一需要执行的信号或者函数。比如,发个信号调用槽函数,这也是事件。

事件循环通俗来说,就是一个公司万金油,没事到处溜达,有事就会迅速处理事情,没事就继续溜达等待下一件事情出现。

当这个线程是以事件循环的方式运行的时候,说明你只需要通过跨线程的信号槽进行控制,任意调用该类的任意槽函数,无论其怎么运行代码,都不会影响主线程,其是作为一条单独额外的线程运行。

可能我说的还不够清楚,你们没什么概念,用图解释吧:

QObject方式:随时调哪个函数方法都行,也可以随时停止,等下一次想调再调,通通都是在子线程运行,不影响主线程。

子线程
主线程
方法4
方法1
方法3
方法2
事件2
事件1
信号

说到这里,想必你已经明白QObject方式的强大了,最后一个解释想必不用我说你们也明白,其正是利用的事件循环。
.

三、同步异步的实现

如果直接跳到这的同学,建议先了解上面的内容。

同步:当两个线程需要访问同一内存区时,为防止相互读写错乱,故弄出个“礼让”行为,A读写B堵塞等,A读写完B来,B读写A堵塞等,B读写完A来…循环往复。

异步:当两个线程需要做的事情毫不干系时,就随其自行运行,A线程和B线程怎么动都不会影响对方。

一般来说,实现同步需要加锁,使用低级原语,但是QObject方式完全可以不需要。

共享内存区
子线程B
子线程A
主线程
4
8
1
2
3
5
6
7
共享内存C
方法B
方法A
B信号
A信号

数字代表执行顺序

信号与槽的运行事件会有延迟10ms左右,如果无法接受这个延迟数值的话,可以自行使用低级原语加锁。

实现异步呢,就是A线程和B线程不用顾虑会不会对同一共享内存进行访问并阻碍到对方的运行。

简析奇妙的Qt多线程,其同时兼备同步和异步_第1张图片

四、跨平台特性的多线程

官方原话:The QThread class provides a platform-independent way to manage threads.

有道译文:QThread类提供了一种平台无关的方式来管理线程。

说明其不受平台限制的方式进行的QThread类实现,凡是使用Qt并能在该平台上运行即可使用该方式。
.

至此简析结束,若有错望指正。

你可能感兴趣的:(Qt之路,qt,c++,ide)