Qt提供QThread类以进行多任务处理。与多任务处理一样,Qt提供的线程可以做到单个线程做不到的事情。例如,网络应用程序中,可以使用线程处理多种连接器。
QThread继承自QObject类,且提供QMutex类以实现同步。线程和进程共享全局变量,可以使用互斥体对改变后的全局变量值实现同步。因此,必须编辑全局数据时,使用互斥体实现同步,其它进程则不能改变或浏览全局变量值。
什么是互斥体?
互斥体实现了“互相排斥”(mutual exclusion)同步的简单形式(所以名为互斥体(mutex))。互斥体禁止多个线程同时进入受保护的代码“临界区”(critical section)。
在任意时刻,只有一个线程被允许进入代码保护区。任何线程在进入临界区之前,必须获取(acquire)与此区域相关联的互斥体的所有权。如果已有另一线程拥有了临界区的互斥体,其他线程就不能再进入其中。这些线程必须等待,直到当前的属主线程释放(release)该互斥体。
什么时候需要使用互斥体呢?
互斥体用于保护共享的易变代码,也就是,全局或静态数据。这样的数据必须通过互斥体进行保护,以防止它们在多个线程同时访问时损坏。
class MyThread : public QThread
{
Q_OBJECT
protected:
void run();
};
void MyThread :: run(){
...
}
如上述代码所示,如果要创建线程,则必须继承QThread类。MyThread使用成员函数run()才会实现线程。
线程类 | 说明 |
---|---|
QAtomicInt | 提供了Integer上与平台无关的Qtomic运算 |
QAtomicPointer | 提供了指针上Atomic运算的模板函数 |
QFuture | 显示异步运算结果的类 |
QFutureSynchronizer | QFuture类简化同步而提供的类 |
QFutureWatcher | 使用信号和槽,允许QFuture监听 |
QMutex | 访问类之间的同步 |
QMutecLocker | 简化Lock和Unlock Mutex的类 |
QReadWriteLock | 控制读写操作的类 |
QReadLocker | 为了读访问而提供的 |
QWriteLocker | 为了写访问而提供的 |
QRunnable | 正在运行的所有对象的父类,且定义了虚函数run() |
QSemaphore | 一般的Count互斥体类 |
QThread | 提供与平台无关的线程功能的类 |
QThreadPool | 管理线程的类 |
QThreadStorage | 提供每个线程存储区域的类 |
QWaitCondition | 确认线程间同步的类的状态值 |
为了同步线程,Qt提供了QMutex、QReadWriteLock、QSemaphore和QWaitCondition类。主线程等待与其他线程的中断时,必须进行同步。例如:两个线程同时访问共享变量,那么可能得不到预想的结果。因此,两个线程访问共享变量时,必须进行同步。
一个线程安全的函数不一定是可重入的;一个可重入的函数缺也不一定是线程安全的!
可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。
编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个线程调用此函数时,很有可能使有关全局变量变为不可知状态。
满足下列条件的函数多数是不可重入的:
常见的不可重入函数有:
也就是说:本质上,可重入性与C++类或者没有全局静态变量的函数相似,由于只能访问自身所有的数据变量区域,所以即使有两个以上线程访问,也可以保证安全性。
QThread类继承自QObjects类。因此,线程开始或结束时,QThread类发生发送信号事件。信号与槽的功能是QThread类从QObject类继承的,可以通过信号与槽处理开始或结束等操作,所以可以实现多线程。QObject是基于QTimer、QTcpSocket、QUdpSocket和QProcess之类的非图形用户界面的子类。
基于非图形用户界面的子类可以无线程操作。单一类运行某功能时,可以不需要线程。但是,运行单一类的目标程序的上级功能时,则必须通过线程实现。
线程A和线程B没有结束的情况下,应设计使主线程时间循环不结束;而若线程A迟迟不结束而导致主线程循环也迟迟不能结束,故也要防止线程A没有在一定时间内结束。
Qt提供了可以决定信号与槽类型的枚举类,以在线程环境中适当处理事物。
常量 | 值 | 说明 |
Qt::AutoConnection | 0 | 如果其他线程中发生信号,则会插入队列,像QueuedConnection一样,否则如DirectConnection一样,直接连接到槽。发送信号时决定Connection类型。 |
Qt::DirectConnection | 1 | 发生信号事件后,槽立即响应 |
Qt::QueuedConnection | 2 | 返回收到的线程事件循环时,发生槽事件。槽在收到的线程中运行 |
Qt::BlockingQueuedConnection | 3 | 与QueuedConnection一样,返回槽时,线程被阻塞。建立在事件发生处使用该类型 |
QtConcurrent类提供多线程功能,不使用互斥体、读写锁、等待条件和信号量等低级线程。使用QtConcurrent创建的程序会根据进程数自行调整使用的线程数。
QThread类提供了与系统无关的线程。
QThread代表在程序中一个单独的线程控制。线程在run()中开始执行,默认情况下,run()通过调用exec()启动事件循环并在线程里运行一个Qt的事件循环。
QThread类可以不受平台影响而实现线程。QThread提供在程序中可以控制和管理线程的多种成员函数和信号/槽。通过QThread类的成员函数start()启动线程。
QThread通过信号函数started()和finished()通知开始和结束,并查看线程状态;可以使用isFinished()和isRunning()来查询线程的状态;使用函数exit()和quit()可以结束线程。
如果使用多线程,有时需要等到所有线程终止。此时,使用函数wait()即可。线程中,使用成员函数sleep()、msleep()和usleep()可以暂停秒、毫秒及微秒单位的线程。
一般情况下,wait()和sleep()函数应该不需要,因为Qt是一个事件驱动型框架。考虑监听finished()信号来取代wait(),使用QTimer来取代sleep()。
静态函数currentThreadId()和currentThread()返回标识当前正在执行的线程。前者返回该线程平台特定的ID,后者返回一个线程指针。
要设置线程的名称,可以在启动线程之前调用setObjectName()。如果不调用setObjectName(),线程的名称将是线程对象的运行时类型(QThread子类的类名)。
可以将常用的接口按照功能进行以下分类:
线程启动
void start(Priority priority = InheritPriority) [slot]
调用后会执行run()函数,但在run()函数执行前会发射信号started(),操作系统将根据优先级参数调度线程。如果线程已经在运行,那么这个函数什么也不做。优先级参数的效果取决于操作系统的调度策略。特别是那些不支持线程优先级的系统优先级将会被忽略(例如在Linux中,更多细节请参考http://linux.die.net/man/2/sched_setscheduler)。
线程执行
int exec() [protected]
进入事件循环并等待直到调用exit(),返回值是通过调用exit()来获得,如果调用成功则范围0。
void run() [virtual protected]
线程的起点,在调用start()之后,新创建的线程就会调用这个函数,默认实现调用exec(),大多数需要重新实现这个函数,便于管理自己的线程。该方法返回时,该线程的执行将结束。
线程退出
void quit() [slot]
告诉线程事件循环退出,返回0表示成功,相当于调用了QThread::exit(0)。
void exit(int returnCode = 0)
告诉线程事件循环退出。 调用这个函数后,线程离开事件循环后返回,QEventLoop::exec()返回returnCode,按照惯例,0表示成功;任何非0值表示失败。
void terminate() [slot]
终止线程,线程可能会立即被终止也可能不会,这取决于操作系统的调度策略,使用terminate()之后再使用QThread::wait(),以确保万无一失。当线程被终止后,所有等待中的线程将会被唤醒。
警告:此函数比较危险,不鼓励使用。线程可以在代码执行的任何点被终止。线程可能在更新数据时被终止,从而没有机会来清理自己,解锁等等。。。总之,只有在绝对必要时使用此函数。
void requestInterruption()
请求线程的中断。该请求是咨询意见并且取决于线程上运行的代码,来决定是否及如何执行这样的请求。此函数不停止线程上运行的任何事件循环,并且在任何情况下都不会终止它。
线程等待
void msleep(unsigned long msecs) [static] //强制当前线程睡眠msecs毫秒
void sleep(unsigned long secs) [static] //强制当前线程睡眠secs秒
void usleep(unsigned long usecs) [static] //强制当前线程睡眠usecs微秒
bool wait(unsigned long time = ULONG_MAX) //线程将会被阻塞,等待time毫秒。和sleep不同的是,如果线程退出,wait会返回。
线程优先级
void setPriority(Priority priority)
设置正在运行线程的优先级。如果线程没有运行,此函数不执行任何操作并立即返回。使用的start()来启动一个线程具有特定的优先级。优先级参数可以是QThread::Priority枚举除InheritPriortyd的任何值。
常量 | 值 | 优先级 |
QThread::IdlePriority | 0 | 没有其它线程运行时才调度 |
QThread::LowestPriority | 1 | 比LowPriority调度频率低 |
QThread::LowPriority | 2 | 比NormalPriority调度频率低 |
QThread::NormalPriority | 3 | 操作系统的默认优先级 |
QThread::HighPriority | 4 | 比NormalPriority调度频繁 |
QThread::HighestPriority | 5 | 比HighPriority调度频繁 |
QThread::TimeCriticalPriority | 6 | 尽可能频繁的调度 |
QThread::InheritPriority | 7 | 使用和创建线程同样的优先级. 这是默认值 |
QThread的使用方法有如下两种:
方法描述:
例子:
#ifndef WORKER_H
#define WORKER_H
#include
#include
#include
class Worker:public QObject //work定义了线程要执行的工作
{
Q_OBJECT
public:
Worker(QObject* parent = nullptr){}
public slots:
void doWork(int parameter) //doWork定义了线程要执行的操作
{
qDebug()<<"receive the execute signal---------------------------------";
qDebug()<<" current thread ID:"<
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include
#include
#include
class Controller : public QObject //controller用于启动线程和处理线程执行结果
{
Q_OBJECT
QThread workerThread;
public:
Controller(QObject *parent= nullptr);
~Controller();
public slots:
void handleResults(const int rslt) //处理线程执行的结果
{
qDebug()<<"receive the resultReady signal---------------------------------";
qDebug()<<" current thread ID:"<
#include "controller.h"
#include
Controller::Controller(QObject *parent) : QObject(parent)
{
Worker *worker = new Worker;
worker->moveToThread(&workerThread); //调用moveToThread将该任务交给workThread
connect(this, SIGNAL(operate(const int)), worker, SLOT(doWork(int))); //operate信号发射后启动线程工作
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); //该线程结束时销毁
connect(worker, SIGNAL(resultReady(int)), this, SLOT(handleResults(int))); //线程结束后发送信号,对结果进行处理
workerThread.start(); //启动线程
qDebug()<<"emit the signal to execute!---------------------------------";
qDebug()<<" current thread ID:"<
方法描述
例子:
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include
#include
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread(QObject* parent = nullptr);
signals: //自定义发送的信号
void myThreadSignal(const int);
public slots: //自定义槽
void myThreadSlot(const int);
protected:
void run() override;
};
#endif // MYTHREAD_H
#include "mythread.h"
MyThread::MyThread(QObject *parent)
{
}
void MyThread::run()
{
qDebug()<<"myThread run() start to execute";
qDebug()<<" current thread ID:"<
#include "controller.h"
#include
Controller::Controller(QObject *parent) : QObject(parent)
{
myThrd = new MyThread;
connect(myThrd,&MyThread::myThreadSignal,this,&Controller::handleResults);
connect(myThrd, &QThread::finished, this, &QObject::deleteLater); //该线程结束时销毁
connect(this,&Controller::operate,myThrd,&MyThread::myThreadSlot);
myThrd->start();
QThread::sleep(5);
emit operate(999);
}
Controller::~Controller()
{
myThrd->quit();
myThrd->wait();
}
两种方法来执行线程都可以,随便你的喜欢。不过看起来第二种更加简单,容易让人理解。不过我们的兴趣在于这两种使用方法到底有什么区别?其最大的区别在于:
启动或终止线程时,QThread提供了信号与槽。
信号 | 含义 |
void finished() | 终止线程实例运行,发送信号 |
void started() | 启动线程实例,发送信号 |
void terminated() | 结束线程实例,则发送信号 |
槽 | 含义 |
void quit() | 线程终止运行槽 |
void start(Priority) | 线程启动槽 |
void terminate() | 线程结束槽 |