参考网上的一些内容,整理QT多线程的一些知识点。
Qt提供了许多处理线程的类和函数。每个程序启动后拥有的第一个线程称为主线程,即GUI线程。QT中所有的组件类和几个相关的类只能工作在GUI线程,不能工作在次线程,次线程即工作线程,主要负责处理GUI线程卸下的工作。
QThread是Qt中所有线程控制的基础,每个QThread实例代表并控制一个线程。QThread可以被直接实例化,也可以被子类化。实例化QThread提供了一个并行事件循环,允许在辅助线程中调用QObject插槽。子类化QThread允许应用程序在启动其事件循环之前初始化新线程,在没有事件循环的情况下运行并行代码。
QThread是Qt线程中有一个公共的抽象类,所有的线程类都是从QThread抽象类中派生的,需要实现QThread中的虚函数run(),通过start()函数来调用run函数。
void exec()进入主事件循环并等待,直到调用exit(),然后返回设置为exit()的值(如果通过quit()调用exit(),则该值为0)。有必要调用此函数来启动事件处理。主事件循环从窗口系统接收事件,并将这些事件分派给应用程序小部件。一般来说,在调用exec()之前不会发生任何用户交互。作为一种特殊情况,像QMessageBox这样的模态小部件可以在调用exec()之前使用,因为模态小部件调用exec()来启动本地事件循环。要使您的应用程序执行空闲处理,即在没有挂起事件时执行一个特殊的函数,请使用超时为0的QTimer。可以使用processEvents()实现更高级的空闲处理方案。我们建议您将清理代码连接到aboutToQuit()信号,而不是将其放在应用程序的main()函数中。这是因为,在某些平台上,QApplication::exec()调用可能不会返回。例如,在Windows平台上,当用户注销时,系统会在Qt关闭所有顶级窗口后终止进程。因此,不能保证应用程序有时间退出其事件循环,并在调用QApplication::exec()之后在main()函数的末尾执行代码。
void run()线程的起点。在调用start()之后,新创建的线程将调用此函数。默认实现只是调用exec()。您可以重新实现此函数以方便高级线程管理。从这个方法返回将结束线程的执行。参见start()和wait()。
void start()函数是启动函数,用于将线程入口地址设置为run函数,启动线程执行,启动后会发出started ()信号。通过调用run()开始线程的执行。操作系统将根据优先级参数调度线程。如果线程已经在运行,则此函数不执行任何操作。优先级参数的效果取决于操作系统的调度策略。特别是,在不支持线程优先级的系统上,优先级将被忽略(比如在Linux上,更多细节请参阅sched_setscheduler文档)。started()信号在关联线程开始执行时,在调用run()函数之前,从该线程发出此信号。注意:这是一个私有信号。它可以用于信号连接,但不能由用户发出。
bool QThread::wait(unsigned long time = ULONG_MAX):阻塞线程,直到满足以下任一条件:与这个QThread对象关联的线程已经完成执行(即当它从run()返回时)。如果线程已经完成,这个函数将返回true。如果线程尚未启动,它还返回true。给定的时间(单位毫秒)已经过去。如果时间是ULONG_MAX(默认值),那么等待将永远不会超时(线程必须从run()返回)。如果等待超时,此函数将返回false。
void quit():告诉线程的事件循环退出,返回代码为0(成功)。相当于调用QThread::exit(0)。如果线程没有事件循环,则此函数不执行任何操作。
QCoreApplication::exec()总是在主线程(执行main()的线程)中被调用,不能从一个QThread中调用。在GUI程序中,主线程也称为GUI线程,是唯一允许执行GUI相关操作的线程。另外,必须在创建一个QThread前创建QApplication(or QCoreApplication)对象。
当线程启动和结束时,QThread会发送信号started()和finished(),可以使用isFinished()和isRunning()来查询线程的状态。从Qt4.8起,可以释放运行刚刚结束的线程对象,通过连接finished()信号到QObject::deleteLater()槽。 使用wait()来阻塞调用的线程,直到其它线程执行完毕(或者直到指定的时间过去)。
静态函数currentThreadId()和currentThread()返回标识当前正在执行的线程。前者返回线程的ID,后者返回一个线程指针。要设置线程的名称,可以在启动线程之前调用setObjectName()。如果不调用setObjectName(),线程的名称将是线程对象的运行时类型(QThread子类的类名)。
QThread线程总共有8个优先级:
QThread::IdlePriority 0 scheduled only when no other threads are running.
QThread::LowestPriority 1 scheduled less often than LowPriority.
QThread::LowPriority 2 scheduled less often than NormalPriority.
QThread::NormalPriority 3 the default priority of the operating system.
QThread::HighPriority 4 scheduled more often than NormalPriority.
QThread::HighestPriority 5 scheduled more often than HighPriority.
QThread::TimeCriticalPriority 6 scheduled as often as possible.
QThread::InheritPriority 7 use the same priority as the creating thread. This is the default.
方法一:定义QThread子类,重写run()函数,调用start()启动线程。
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
class MyThread1 : public QThread {
public:
virtual void run();
};
void MyThread1::run()
{
while(1) {
sleep( 1 );
qDebug()<<"current thread is:"<<QThread::currentThreadId();
}
}
class MyThread2 : public QThread {
public:
virtual void run();
};
void MyThread2::run()
{
while(1) {
sleep( 1 );
qDebug()<<"current thread is:"<<QThread::currentThreadId();
}
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug("current thread is %p \n:",QThread::currentThreadId());
MyThread1 thrd1;
MyThread2 thrd2;
//启动线程,自动调用run()
thrd1.start();
thrd2.start();
//要等待线程a,b都退出
thrd1.wait();
thrd2.wait();
return a.exec();
}
方法二:利用void QObject::moveToThread(QThread *targetThread)函数。QT推荐用法。
更改此对象及其子对象的线程关联。如果对象有父对象,则不能移动该对象。事件处理将在targetThread中继续。要将一个对象移动到主线程,使用QApplication::instance()来检索指向当前应用程序的指针,然后使用QApplication::thread()来检索应用程序所在的线程。例如:
myObject->moveToThread(QApplication::instance()->thread());
如果targetThread为零,则此对象及其子对象的所有事件处理将停止。注意,该对象的所有活动计时器将被重置。计时器首先在当前线程中停止,然后在targetThread中(以相同的时间间隔)重新启动。因此,在线程之间不断移动对象可能会无限期地延迟计时器事件。就在线程关联被更改之前,一个QEvent::ThreadChange事件被发送到这个对象。您可以处理此事件来执行任何特殊处理。注意,任何提交到此对象的新事件都将在targetThread中处理。警告:这个函数不是线程安全的;当前线程必须与当前线程关联相同。换句话说,这个函数只能将一个对象从当前线程“推”到另一个线程,而不能将一个对象从任意线程“拉”到当前线程。(翻译自Qt 5.9.0 Reference Documentation,晦涩难懂!搞糊涂了)。
根据网友的分析, myObject->moveToThread(QApplication::instance()->thread())不是将myObject所有工作“移动”到了QApplication::instance()->thread(),而是将所有myObject相关的事件托管到QApplication::instance()->thread()线程执行。
以下是官方给出的应用模板:
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 &);
};
[static] QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
创建从发送方对象中的信号到接收方对象中的方法的给定类型的连接。返回连接的句柄,用于稍后断开连接。例如,在指定信号和方法时,必须使用SIGNAL()和SLOT()宏:
QLabel *label = new QLabel;
QScrollBar *scrollBar = new QScrollBar;
QObject::connect(scrollBar, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)));
此示例确保标签始终显示当前滚动条的值。注意,信号和插槽参数不能包含任何变量名,只能包含类型。例如,下列语句不能工作并返回false
// WRONG
QObject::connect(scrollBar, SIGNAL(valueChanged(int value)),
label, SLOT(setNum(int value)));
一个信号也可以连接到另一个信号:
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget();
signals:
void buttonClicked();
private:
QPushButton *myButton;
};
MyWidget::MyWidget()
{
myButton = new QPushButton(this);
connect(myButton, SIGNAL(clicked()),
this, SIGNAL(buttonClicked()));
}
在本例中,MyWidget构造函数中继来自私有成员变量的信号,并使其在与MyWidget相关的名称下可用。