简述
多线程与多进程是现代操作系统中非常重要的两个概念,多进程强调分工,多线程强调合作,本文只谈多线程。
多线程有硬件多线程和软件多线程之分,下面只谈软件多线程。
多线程的能力一般是由操作系统(如Windows、Linux)提供,不同的操作系统在多线程的实现上是不同的,而且呈现出来的接口也是不统一的。很多程序开发框架都提供了独立于操作系统平台的多线程接口。
Qt的QThread提供了一个独立于平台的方法管理多线程。一个QThread对象管理着一个线程。QThread通过执行run()开始。默认情况下,run()通过调用exec()开启事件循环。
基本用法
方法1
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 &);
};
上面的程序中有两个类:Worker、Controller。Worker中的doWork()成员函数会做一些耗时的操作。
在Controller中实例化一个Worker。再实例化一个QThread作为Worker的执行线程。通过moveToThread将worker绑定到workerThread。
worker中的槽函数将会在独立的线程workerThread中运行。我们可以将worker的槽函数绑定到来自任何线程的任何对象的任何信号。跨线程的信号槽连接是安全的。
方法2
使用QThread的另一个方法是通过子类化QThread并重写run(),如下所示
class WorkerThread : public QThread
{
Q_OBJECT
void run() override {
QString result;
/* ... here is the expensive or blocking operation ... */
emit resultReady(result);
}
signals:
void resultReady(const QString &s);
};
void MyObject::startWorkInAThread()
{
WorkerThread *workerThread = new WorkerThread(this);
connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
}
在上面的例子中,线程将会在run()函数返回后结束。除非在run中调用.exec(),否则将不会有任何的事件循环在该线程中运行。
在方法2中,QThread实例存在于旧的线程中,而不在新的线程中。这意味着QThread的所有槽函数和引用方法都会在旧的线程中被执行。因此,如果开发者想要在新的线程中引用槽函数就必须采用方法1。新的槽函数不应当被直接实例化到QThread的子类中。
当子类化QThread的时候,记住,构造函数在旧的线程中执行,run()在新的线程中执行。如果两个函数都用到了某个变量,那么这个变量就是在被多个线程使用。请确认这样做是安全的。
管理线程
QThread通过信号started()和finished()来提示线程的开始和结束。也可以通过isFinished()和isRunning()来主动请求线程的状态。
可以通过exit()和quit()来停止线程。在极端的情况下,可以通过terminate()来强制结束一个线程。但是,这样做是很危险的,尽量不要这样做。
从Qt 4.8开始,释放一个结束线程中的资源变得非常方便。只需要绑定finished()和QObject::deleteLater()。
通过wait()来阻塞一个线程,直到其它线程结束执行。
QThread也提供了静态的、平台独立的睡眠函数:sleep(),msleep(),和usleep()。
注意
:由于Qt是一个事件驱动的框架,尽量用finished(),而不是wait(),尽量用QTimer,而不是sleep()。
静态函数currentThreadID和currentThread返回当前线程的标识符,前者返回平台独立的特定ID,后者返回QThread指针。
setPriority用来设置进程优先级,setStackSize用来设置栈深。
扩展阅读
引用
[1] Qt助手
[2] 维基百科–多线程