一、线程管理
1、线程启动
void start(Priority priority = InheritPriority)
调用后会执行run()函数,但在run()函数执行前会发射信号started(),操作系统将根据优先级参数调度线程。如果线程已经在运行,那么这个函数什么也不做。优先级参数的效果取决于操作系统的调度策略。特别是那些不支持线程优先级的系统优先级将会被忽略(例如在Linux中,更多细节请参考http://linux.die.net/man/2/sched_setscheduler)。
2、线程执行
int exec()
进入事件循环并等待直到调用exit(),返回值是通过调用exit()来获得,如果调用成功则范围0。
virtual void run();
线程的起点,在调用start()之后,新创建的线程就会调用这个函数,默认实现调用exec(),大多数需要重新实现这个功能,便于管理自己的线程。该方法返回时,该线程的执行将结束。
3、线程退出
void quit()
告诉线程事件循环退出,返回0表示成功,相当于调用了QThread::exit(0)。
void exit(int returnCode = 0)
告诉线程事件循环退出。
调用这个函数后,线程离开事件循环后返回,QEventLoop::exec()返回returnCode,
按照惯例0表示成功,任何非0值表示失败。
void terminate()
终止线程,线程可能会立即被终止也可能不会,这取决于操作系统的调度策略,使用terminate()之后再使用QThread::wait()确保万无一失。
当线程被终止后,所有等待中的线程将会被唤醒。
警告:此功能比较危险,不鼓励使用。线程可以在代码执行的任何点被终止。线程可能在更新数据时被终止,从而没有机会来清理自己,解锁等等。。。总之,只有在绝对必要时使用此功能。
建议:一般情况下,都在run函数里面设置一个标识符,可以控制循环停止。然后才调用quit函数,退出线程。
4、线程等待
void msleep(unsigned long msecs)
强制当前线程睡眠msecs毫秒
void sleep(unsigned long secs)
强制当前线程睡眠secs秒
void usleep(unsigned long usecs)
强制当前线程睡眠usecs微秒
bool wait(unsigned long time = ULONG_MAX);
线程将会被阻塞,等待time毫秒。和sleep不同的是,如果线程退出,wait会返回。
5、线程状态
bool isFinished() const
线程是否结束
6、线程优先级
Constant | Value | Description |
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 | 使用和创建线程同样的优先级. 这是默认值. |
在Qt之线程(QThread)一节中我介绍了QThread 的两种使用方法:
1、子类化 QThread(不使用事件循环)。
这是官方手册、例子以及相关书籍中都介绍的一种常用的方法。
a. 子类化 QThread
b. 重载 run 函数,run函数内有一个while或for的死循环(模拟耗时操作)
c. 设置一个标记为来控制死循环的退出。
2、子类化 QObject
a. 子类化 QObject
b. 定义槽函数
c. 将该子类的对象moveToThread到新线程中
那么,线程中的槽函数是怎么运行的呢?
Constant | Value | Description |
Qt::DirectConnection | 1 | 直接连接:当信号发射时,槽函数将直接被调用。无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行。 |
Qt::QueuedConnection | 2 | 队列连接:当控制权回到接受者所依附线程的事件循环时,槽函数被调用。槽函数在接收者所依附线程执行。也就是说:这种方式既可以在线程内传递消息,也可以跨线程传递消息 |
Qt::BlockingQueuedConnection | 3 | 与Qt::QueuedConnection类似,但是会阻塞等到关联的slot都被执行。这里出现了阻塞这个词,说明它是专门用来多线程间传递消息的。 |
举例:
MyObject.h
#ifndef MYOBJECT_H #define MYOBJECT_H #include class MyObject : public QObject { Q_OBJECT public: explicit MyObject(QObject *parent = 0); public slots: void start(); }; #endif // MYOBJECT_H
MyObject.cpp
#include "MyObject.h"
#include
#include
MyObject::MyObject(QObject *parent)
: QObject(parent)
{
}
void MyObject::start()
{
qDebug() << QString("my object thread id:") << QThread::currentThreadId();
}
main.cpp
#include "MyObject.h"
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
qDebug() << QString("main thread id:") << QThread::currentThreadId();
MyObject object;
QThread thread;
object.moveToThread(&thread);
QObject::connect(&thread, SIGNAL(started()), &object, SLOT(start()));
thread.start();
return a.exec();
}
"main thread id:" 0xf08
"my object thread id:" 0x216c
显然主线程与槽函数的线程是不同的,因为moveToThread后MyObject所在的线程为QThread,继上面介绍的thread.start()执行后首先会发射started()信号,也就是说started()信号发射是在次线程中进行的,所以无论采取Qt::AutoConnection、Qt::DirectConnection、Qt::QueuedConnection哪种连接方式,主线程与槽函数的线程都是不同的。
1、修改代码如下:
MyObject object;
QThread thread;
//object.moveToThread(&thread);
QObject::connect(&thread, SIGNAL(started()), &object, SLOT(start()), Qt::DirectConnection);
thread.start();
"main thread id:" 0x2688
"my object thread id:" 0x2110
显然主线程与槽函数的线程是不同的,MyObject所依附的线程为主线程(因为注释掉了moveToThread),继上面介绍的Qt::DirectConnection(无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行)。也就是说started()信号发射是在次线程中进行的,槽函数也是在次线程中进行的,所以主线程与槽函数的线程是不同的。
2、修改代码如下:
MyObject object;
QThread thread;
//object.moveToThread(&thread);
QObject::connect(&thread, SIGNAL(started()), &object, SLOT(start()), Qt::QueuedConnection);
thread.start();
查看运行结果:
"main thread id:" 0x24ec
"my object thread id:" 0x24ec
显然主线程与槽函数的线程是相同的,继上面介绍的Qt::QueuedConnection(槽函数在接收者所依附线程执行)。也就是说started()信号发射是在次线程中进行的,但MyObject所依附的线程为主线程(因为注释掉了moveToThread),所以主线程与槽函数的线程必然是相同的。
3、修改代码如下:
MyObject object;
QThread thread;
//object.moveToThread(&thread);
QObject::connect(&thread, SIGNAL(started()), &object, SLOT(start()), Qt::AutoConnection);
thread.start();
"main thread id:" 0x2700
"my object thread id:" 0x2700
显然主线程与槽函数的线程是相同的,MyObject所依附的线程为主线程(因为注释掉了moveToThread),继上面介绍的Qt::AutoConnection(如果信号在接收者所依附的线程内发射,则等同于直接连接。如果发射信号的线程和接受者所依附的线程不同,则等同于队列连接。)。因为started()信号和MyObject依附的线程不同,所以结果和Qt::QueuedConnection对应的相同,所以主线程与槽函数的线程是相同的。
---------------------------------------------------------------------------------------------------
概述
如果想对Qt中的QThread有个更加深刻的了解,必须要知道这几个重要的函数,现在就一一介绍下。
函数介绍
属性 返回值 函数体 功能
static QThread * QThread::currentThread() 返回当前线程的指针,静态函数。
static Qt::HANDLE QThread::currentThreadId() 返回当前线程的句柄,静态函数
static bool QThread::isFinished() const 如果线程执行结束,返回true,否则返回false
static bool QThread::isRunning() const 如果当前线程在运行中,返回true,否则返回false
static int QThread::idealThreadCount() 返回理理想状态下该系统支持的线程的数量。如果无发现检测到处理器的核数,返回值为-1
protected int exec() 使线程进入事件循环状态,并且处于wait状态,直到调用exit()函数使其退出。退出时返回值是调用exit()函数时的输入参数。如果调用quit()函数,其退出的返回值为0.该函数一般在run()函数中调用,使线程进入事件循环处理状态。
protected void exit(int returnCode = 0) 告知线程从事件循环状态退出,并且返回returnCode的值。一般说来,返回0表示成功退出,返回非0值表示遇到错误。调用该函数后,线程不会再进行事件处理,除非再次调用exec()函数。如果当前线程不处于执行状态,那么下次调用exec()也会直接返回
private signal void finished() 在线程执行完毕前发出该信号,当发出该信号时,意味着线程早已经退出了事件循环处理状态,即不再处理除了延迟删除事件(deferred deletion)之外的任何事件。可以把该信号和QObject::deleteLater()连接起来用来删除线程中的对象。如果强制使用terminate()函数来结束线程,那么将无法得知finish()信号的发送线程。另外,这是一个私有信号,所以用户无法发出这个信号。
static bool QThread::isInterruptionRequested() const 返回当前线程上运行的任务是否可以停止,可以通过requestInterruption()来进行中断查询。该函数可以使得长时间运行的任务彻底停止,永远不要检查该函数的返回值是否是安全的。但是进行长时间任务时提倡有规律的使用该方法。但是不要频繁使用该方法避免线程切换的开销。
static bool QThread::isRunning() const 返回当前线程是否正在运行,如果在运行,返回true,否则返回false
static int QThread::loopLevel() const 该函数返回当前事件循环的层级,但是该函数只能在线程内部调用。
static void QThread::msleep(unsigned long msecs) 强制线程休息msecs毫秒
static void QThread::usleep(unsigned long usecs) 强制线程休息usecs微秒
static void QThread::msleep(unsigned long secs) 强制线程休息secs秒
virtual protected void QThread::run() 该函数是线程运行的起始点。调用start()函数之后,新创建的函数调用该函数,默认的QThread简单的调用exec()进入事件循环机制。我们可以重载这个函数来实现更高级的线程管理方式。从该函数返回将结束线程。
static void QThread::setPriority(Priority priority) 该函数设置线程运行的优先级,如果线程并没有处于运行状态,该函数什么也不做。可以通过调用start()来指定一个优先级来启动线程。优先级的效果依赖于操作系统的调度方式,尤其是在不支持优先级的操作系统上,优先级设置会被忽略。
slot void QThread::quit() 告诉线程的事件循环退出并且返回0值,相当于调用QThread::exit(0)。如果线程没有事件循环,这个函数则什么也不做。
virtual protected void QThread::setStackSize(uint stackSize) 设置线程堆栈的最大值,如果设置的值大于0,那么就会将堆栈最大值设置为当前数值。否则,线程堆栈的最大值由操作系统来决定。警告:大部分操作系统都会自己设置线程堆栈的最大最小值,如果设置的堆栈大小超出范围,线程则不能启动。
static protected void QThread::setTerminationEnabled(bool enabled = true)
slot void QThread::start(Priority priority = InheritPriority) 通过调用run()函数启动线程。操作系统会根据优先级来调度线程。如果线程已经处于运行状态,该函数什么也不做。优先级设置依赖于操作系统的线程调度方式。
signal void QThread::started() 线程开始执行时发出该信号,发出时间在run()函数调用之前。注意:这是一个私有信号,因此只能由线程发出,用户不能发出该信号。
slot void QThread::terminate() 终止当前线程。线程或许不会立即被终止,依赖于线程的调度策略。一般情况下,调用该函数之后再调用QThread::wait()来确保线程结束。该线程终止后,等待该线程的其他线程将被唤醒。警告:该函数比较危险,不推荐这样做。线程可能在任何代码处终止。或许在修改数据时被终止,线程结束后自己不能去做清理工作。
static bool QThread::wait(unsigned long time = ULONG_MAX) 阻塞当前的进程,直到满足如下两个条件之一: 1.相关的线程完成其任务,然后如果线程已经结束,则该函数返回true,如果线程没有启动,则该函数也会返回true。 2. 经过了特定长度的时间,如果时间是ULONG_MAX(默认值),那么wait()函数几乎不会超时。(即该函数必须从run()函数返回)如果wait函数超时,那么该函数会返回false。
static void QThread::yieldCurrentThread() 将当前线程的执行权让给别的可执行线程。至于让给哪一个可执行线程,那就是操作系统的事情了。
使用技巧
一般调用quit()函数之后可以紧接着调用wait()函数确保线程退出。
sleep()等让线程休眠的函数不需要调用,因为Qt中线程是事件驱动机制。但是如果是继承的QTHread类,在run()函数中使用了无限循环的方式,可以考虑msleep()函数来使线程休息一段时间,一般为1毫秒。