QThread看似类似于C++11的std::thread,实则大不相同。std::thread可以运行一个用户指定的函数;可以说比较的底层。而QThread则根本没有给出任何接口让你有机会指定你想运行的函数,因为它根本就不是为了运行用户指定的函数而生的,而是作为一个线程管理者,而很奇怪的是,这个管理者并不让你有机会指定你想运行的函数。也许有人说,继承QThread,然后重载run()函数,这不就可以了?事实上,这并不是正确的做法,也不是QThread存在的初衷。
那么,究竟怎样是正确的使用QThread呢?怎样才能让用户指定的函数作为一个独立线程运行起来呢?这篇QT的博客给出了详尽的解释。本文不打算再把此博文翻译一遍,而是打算给出一点自己的代码,当然,也是在原博文基础上做了一点点修改。最后指出一个原博文中没有提到的小坑。
首先,简单介绍一下使用QThread的正确方法:
第一步,写一个自己的类,继承QObject,然后在此类中定义一个自己想运行的方法,起什么名字都可以,没有限制。比如,在下文中,笔者将这个方法命名为void run().
第二步,因为此自定义类是继承了QObject的,所以可以调用moveToThread(QThread)方法,将整个自定义类的对象移到该QThread中。
第三步,做一系列的connect,主要是将QThread类的signal和自定义类的slot连接起来,详情可参见下面的代码。
第四步,调用QThread的start()启动新线程。
第五步,调用QThread的wait()方法使主线程阻塞。
具体详情请参见如下代码。
介绍一下整个工程。工程是QT的Console工程,除了.pro文件之外,还有三个文件:worker.h, worker.cpp和main.cpp. 具体代码如下:
1. TestQMutexQThread.pro
- QT += core
- QT -= gui
- CONFIG += c++11
- TARGET = TestQSemaphore
- CONFIG += console
- CONFIG -= app_bundle
- TEMPLATE = app
- SOURCES += main.cpp \
- worker.cpp
- HEADERS += \
- worker.h
QT += core
QT -= gui
CONFIG += c++11
TARGET = TestQSemaphore
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp \
worker.cpp
HEADERS += \
worker.h
- #ifndef MYTHREAD_H
- #define MYTHREAD_H
- #include <QThread>
- #include <QMutex>
- #include <QUuid>
- #include <QFile>
- #include <QDir>
- class Worker : public QObject
- {
- Q_OBJECT
- private:
- static int myValue;
- static QMutex leds_mutex;
- QString m_id;
- public:
- Worker(QObject* parent = nullptr);
- ~Worker();
- public slots:
- void run();
- signals:
- void finished();
- void error(QString err);
- };
- #endif // MYTHREAD_H
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <QMutex>
#include <QUuid>
#include <QFile>
#include <QDir>
class Worker : public QObject
{
Q_OBJECT
private:
static int myValue;
static QMutex leds_mutex;
QString m_id;
public:
Worker(QObject* parent = nullptr);
~Worker();
public slots:
void run();
signals:
void finished();
void error(QString err);
};
#endif // MYTHREAD_H
3. worker.cpp
- #include "worker.h"
- #include <QDebug>
- int Worker::myValue = 0;
- QMutex Worker::leds_mutex;
- Worker::Worker(QObject* parent)
- : QObject(parent)
- , m_id(QUuid::createUuid().toString())
- {
- }
- Worker::~Worker()
- {
- }
- void Worker::run()
- {
- int count = 0;
- while (count < 5) {
- ++count;
- Worker::leds_mutex.lock();
- QFile file;
- QDir::setCurrent("C:/Users/YourUserName/Desktop");
- file.setFileName("1.txt");
- if (!file.open(QIODevice::Append | QIODevice::Text)) {
- qCritical() << "Failed to open file";
- Worker::leds_mutex.unlock();
- break;
- }
- QTextStream out(&file);
- out << "id: " << m_id << ", myValue = " << Worker::myValue << '\n';
- qInfo() << "id: " << m_id << ", myValue = " << Worker::myValue;
- ++Worker::myValue;
- Worker::leds_mutex.unlock();
- }
- emit finished();
- }
#include "worker.h"
#include <QDebug>
int Worker::myValue = 0;
QMutex Worker::leds_mutex;
Worker::Worker(QObject* parent)
: QObject(parent)
, m_id(QUuid::createUuid().toString())
{
}
Worker::~Worker()
{
}
void Worker::run()
{
int count = 0;
while (count < 5) {
++count;
Worker::leds_mutex.lock();
QFile file;
QDir::setCurrent("C:/Users/YourUserName/Desktop");
file.setFileName("1.txt");
if (!file.open(QIODevice::Append | QIODevice::Text)) {
qCritical() << "Failed to open file";
Worker::leds_mutex.unlock();
break;
}
QTextStream out(&file);
out << "id: " << m_id << ", myValue = " << Worker::myValue << '\n';
qInfo() << "id: " << m_id << ", myValue = " << Worker::myValue;
++Worker::myValue;
Worker::leds_mutex.unlock();
}
emit finished();
}
4. main.cpp
- #include <QCoreApplication>
- #include <QDebug>
- #include <QScopedPointer>
- #include "worker.h"
- int main(int argc, char *argv[])
- {
- QCoreApplication a(argc, argv);
- QThread* t1 = new QThread;
- QThread* t2 = new QThread;
- Worker* w1 = new Worker;
- Worker* w2 = new Worker;
- w1->moveToThread(t1);
- w2->moveToThread(t2);
- QObject::connect(t1, &QThread::started, w1, &Worker::run);
- QObject::connect(w1, &Worker::finished, t1, &QThread::quit);
- QObject::connect(w1, &Worker::finished, [](){qInfo() << "w1 quit......";});
- QObject::connect(w1, &Worker::finished, w1, &Worker::deleteLater);
- QObject::connect(t1, &QThread::finished, t1, &QThread::deleteLater);
- QObject::connect(t2, &QThread::started, w2, &Worker::run);
- QObject::connect(w2, &Worker::finished, t2, &QThread::quit);
- QObject::connect(w2, &Worker::finished, [](){qInfo() << "w2 quit......";});
- QObject::connect(w2, &Worker::finished, w2, &Worker::deleteLater);
- QObject::connect(t2, &QThread::finished, t2, &QThread::deleteLater);
- t1->start();
- t2->start();
- t1->wait(3);
- t2->wait(3);
- qInfo() << "At the end of main thread...";
- return 0;
- }
#include <QCoreApplication>
#include <QDebug>
#include <QScopedPointer>
#include "worker.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThread* t1 = new QThread;
QThread* t2 = new QThread;
Worker* w1 = new Worker;
Worker* w2 = new Worker;
w1->moveToThread(t1);
w2->moveToThread(t2);
QObject::connect(t1, &QThread::started, w1, &Worker::run);
QObject::connect(w1, &Worker::finished, t1, &QThread::quit);
QObject::connect(w1, &Worker::finished, [](){qInfo() << "w1 quit......";});
QObject::connect(w1, &Worker::finished, w1, &Worker::deleteLater);
QObject::connect(t1, &QThread::finished, t1, &QThread::deleteLater);
QObject::connect(t2, &QThread::started, w2, &Worker::run);
QObject::connect(w2, &Worker::finished, t2, &QThread::quit);
QObject::connect(w2, &Worker::finished, [](){qInfo() << "w2 quit......";});
QObject::connect(w2, &Worker::finished, w2, &Worker::deleteLater);
QObject::connect(t2, &QThread::finished, t2, &QThread::deleteLater);
t1->start();
t2->start();
t1->wait(3);
t2->wait(3);
qInfo() << "At the end of main thread...";
return 0;
}
好了,源代码就这么多。自己定义的这个Worker::run()方法到底是如何作为一个线程跑起来的呢?关键点就在于main.cpp当中的这一堆的connect语句了。 首先,因为Worker类是继承了QObject类的,所以它可以调用moveToThread(QThread)方法将自身移到一个指定的QThread中去,而这个QThread要想真正运行起来,必须要调用start()方法。一旦调用了QThread的start方法, 就会有QThread的started信号(signal)发射出去,于是便会trigger这个Worker类的run()函数了。
以上这一段就是全部的精华了。
最后一个小坑:注意:在主线程中,有t1->wait(3)和t2->wait(3)这2句话。去查一下手册吧,wait()函数到底是干嘛的。其作用主要是使得主线程阻塞住,等待指定线程,即t1和t2的执行结束,或者超时。这都很好理解。注意,如果没有这2句wait(),主线程由于执行的太快,当子线程还没执行结束,主线程就已经结束退出进而导致整个进程结束了,那子线程自然也就终止了。不过,以上这些还不是笔者说的小坑。这里的小坑是,wait()必须给它填一个参数!如果不填参数的话,当然它有一个默认的参数,但是会导致子线程即使结束,主线程也仍然并将永远处于阻塞状态。这个不知道是否是QT的一个bug.但是,给wait()填一个数字作为参数,即使填上300,代表300秒,主线程也不会真正等上5分钟,而只是一旦子线程结束,主线程也就会解除阻塞,继续往下执行了。
(完)