老实说我不大想写这个的,因为麻烦,但是嘛,我记得有一次面试的时候,面试官听到我说我用的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 ¶meter) {
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助手的官方文档
.
大家乍一看代码可能有点懵,看不懂,我慢慢分析给你看。
其使用的重点在于这里
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();
moveToThread是核心重点,将你实际要使用的线程即Worker类放到workerThread线程里。
当workerThread运行线程的时候,Worker就会在workerThread里其中一个“运行节点”运行。
这怎么理解呢?看下图,Worker就是你写的实际运行类,workerThread实例就是所代表的QThread
有些人可能不明白,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事件循环。
这说明了什么呢?想必很多人已经恍然大悟,或者懵懂。
不打哑谜,这意味着,通过这个方法开启的线程是通过事件循环的方法进行的线程运行!
事件循环,打个简单的例子:你正常使用的Qt主线程,运行方式就是通过事件循环来进行的。
事件循环:在没有事件时等待事件,出现事件时进行处理,处理完继续等待下一个事件。 ——溪渣渣_梁世华
什么是事件?事件就是任一需要执行的信号或者函数。比如,发个信号调用槽函数,这也是事件。
事件循环通俗来说,就是一个公司万金油,没事到处溜达,有事就会迅速处理事情,没事就继续溜达等待下一件事情出现。
当这个线程是以事件循环的方式运行的时候,说明你只需要通过跨线程的信号槽进行控制,任意调用该类的任意槽函数,无论其怎么运行代码,都不会影响主线程,其是作为一条单独额外的线程运行。
可能我说的还不够清楚,你们没什么概念,用图解释吧:
QObject方式:随时调哪个函数方法都行,也可以随时停止,等下一次想调再调,通通都是在子线程运行,不影响主线程。
说到这里,想必你已经明白QObject方式的强大了,最后一个解释想必不用我说你们也明白,其正是利用的事件循环。
.
如果直接跳到这的同学,建议先了解上面的内容。
同步:当两个线程需要访问同一内存区时,为防止相互读写错乱,故弄出个“礼让”行为,A读写B堵塞等,A读写完B来,B读写A堵塞等,B读写完A来…循环往复。
异步:当两个线程需要做的事情毫不干系时,就随其自行运行,A线程和B线程怎么动都不会影响对方。
一般来说,实现同步需要加锁,使用低级原语,但是QObject方式完全可以不需要。
数字代表执行顺序
信号与槽的运行事件会有延迟10ms左右,如果无法接受这个延迟数值的话,可以自行使用低级原语加锁。
实现异步呢,就是A线程和B线程不用顾虑会不会对同一共享内存进行访问并阻碍到对方的运行。
官方原话:The QThread class provides a platform-independent way to manage threads.
有道译文:QThread类提供了一种平台无关的方式来管理线程。
说明其不受平台限制的方式进行的QThread类实现,凡是使用Qt并能在该平台上运行即可使用该方式。
.
至此简析结束,若有错望指正。