回顾Qt之线程(QThread),里面讲解了如何使用线程,但还有很多人留言没有看明白,那么今天我们来一起瞅瞅关于QThread管理线程的那些事儿。。。一、线程管理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
线程是否结束 bool isRunning() const 线程是否正在运行 6、线程优先级
void setPriority(Priority priority) 这个函数设置正在运行线程的优先级。如果线程没有运行,此功能不执行任何操作并立即返回。使用的start()来启动一个线程具有特定的优先级。 优先级参数可以是QThread::Priority枚举除InheritPriortyd的任何值。 Priority priority() const 下面来看下优先级中的各个枚举值:
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到新线程中
run 对于线程的作用相当于main函数对于应用程序。它是线程的入口,run的开始和结束意味着线程的开始和结束。
采用这两种做法,毫无疑问都会在次线程中运行(这里说的是,run中的逻辑以及子类化QObject后连接通过moveToThread然后连接到QThread的started()信号的槽函数,这个下面会详细讲解)。
那么,线程中的槽函数是怎么运行的呢?
说到信号与槽,大家应该再熟悉不过了,包括我,特别喜欢使用自定义信号与槽,感觉用起来特方便、特棒。。。
经常使用,你能否100%的使用正确?你了解它的高级用法吗?
1、你是否在多次connect,还发现不了为什么槽函数会执行那N多次。
2、你是否了解disconnect
3、你是否了解connect中的第五个参数
Qt::ConnectionType 关于connect、disconnect信号、槽的使用可参考:Qt之信号与槽。既然谈到线程这里需要重点说下Qt::ConnectionType(信号与槽的传递方式)
Constant
Value
Description
Qt::AutoConnection
0
自动连接:(默认值)如果信号在接收者所依附的线程内发射,则等同于直接连接。如果发射信号的线程和接受者所依附的线程不同,则等同于队列连接。
Qt::DirectConnection
1
直接连接:当信号发射时,槽函数将直接被调用。无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行。
Qt::QueuedConnection
2
队列连接:当控制权回到接受者所依附线程的事件循环时,槽函数被调用。槽函数在接收者所依附线程执行。也就是说:这种方式既可以在线程内传递消息,也可以跨线程传递消息
Qt::BlockingQueuedConnection
3
与Qt::QueuedConnection类似,但是会阻塞等到关联的slot都被执行。这里出现了阻塞这个词,说明它是专门用来多线程间传递消息的。
举例:
MyObject.hMyObject.cppmain.cpp查看运行结果:
"main thread id:" 0xf08
"my object thread id:" 0x216c
显然主线程与槽函数的线程是不同的(你可以多次尝试,屡试不爽。。。),因为moveToThread后MyObject所在的线程为QThread,继上面介绍的thread.start()执行后首先会发射started()信号,也就是说started()信号发射是在次线程中进行的,所以无论采取Qt::AutoConnection、Qt::DirectConnection、Qt::QueuedConnection哪种连接方式,主线程与槽函数的线程都是不同的。
1、修改代码如下:
查看运行结果:
"main thread id:" 0x2688
"my object thread id:" 0x2110
显然主线程与槽函数的线程是不同的,MyObject所依附的线程为主线程(因为注释掉了moveToThread),继上面介绍的Qt::DirectConnection(无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行)。也就是说started()信号发射是在次线程中进行的,槽函数也是在次线程中进行的,所以主线程与槽函数的线程是不同的。
2、修改代码如下:
查看运行结果:
"main thread id:" 0x24ec
"my object thread id:" 0x24ec
显然主线程与槽函数的线程是相同的,继上面介绍的Qt::QueuedConnection(槽函数在接收者所依附线程执行)。也就是说started()信号发射是在次线程中进行的,但MyObject所依附的线程为主线程(因为注释掉了moveToThread),所以主线程与槽函数的线程必然是相同的。
3、修改代码如下:
查看运行结果:
"main thread id:" 0x2700
"my object thread id:" 0x2700
显然主线程与槽函数的线程是相同的,MyObject所依附的线程为主线程(因为注释掉了moveToThread),继上面介绍的Qt::AutoConnection(如果信号在接收者所依附的线程内发射,则等同于直接连接。如果发射信号的线程和接受者所依附的线程不同,则等同于队列连接。)。因为started()信号和MyObject依附的线程不同,所以结果和Qt::QueuedConnection对应的相同,所以主线程与槽函数的线程是相同的。
基本就介绍到这里,QThread使用和上面的大同小异,run里面执行的代码都是在次线程中,如果是QThead的槽函数,那么结论同上!
this->serverDevice = new tcpServerDevice();
QThread *thread_server = new QThread();
connect(this->serverDevice,SIGNAL(destroyed(QObject*)),thread_server,SLOT(quit()));
connect(thread_server,SIGNAL(finished()),thread_server,SLOT(deleteLater()));
connect(this->serverDevice,SIGNAL(signal_client_newconnect_to_ui(int,QString,quint16)),SLOT(newClientConnet_solt(int,QString,quint16)));
connect(this->serverDevice,SIGNAL(signal_client_disconnect_to_ui(int)),this,SLOT(slot_list_clientfd_remove(int)));
connect(this->serverDevice,SIGNAL(signal_send_data_to_ui(int,QByteArray)),this,SLOT(receiveDatFromDevice_slot(int,QByteArray)));
connect(this,SIGNAL(signal_open_server()),this->serverDevice,SLOT(slot_open_server()));
connect(this,SIGNAL(signal_close_server()),this->serverDevice,SLOT(slot_close_server()));
connect(this,SIGNAL(sendDataToDevice_signal(int,QByteArray)),this->serverDevice,SLOT(slot_send_data_to_client(int,QByteArray)));
this->serverDevice->moveToThread(thread_server);
thread_server->start();
第一部分:QT线程池的构建与使用
网上关于QT线程池QThreadPool的文章很多,而且大都千篇一律,基本上都是参考QT的帮助文档介绍QT全局线程池的用法。这样就往往会使人产生误解,QT是不是推荐大家使用其全局线程池,而不推荐使用自定义构造的线程池? 实际情况并不是这样的。而且在实际的项目当中我们通常并不希望仅仅使用一个全局的线程池,而是在需要线程池的工程中都构建和维护自己一个小小的线程池(我们知道一个良好架构的项目通常是由多个工程组成的)。综上,我们来分析以下两个问题:
(1) 非全局的线程池如果构建与使用呢?
上述程序,构建了一个线程最大数量为3的本地线程池。每间隔1s的时间创建一个线程任务并置入到线程池的任务队列中(QT内部机制实现该队列,我们只需要调用QThreadPool的start函数置入即可)。每个线程任务的持续时间为5s。
(2) 程序当中QRunnable是以指针的形式创建的,该指针是需要程序员去释放,还是QThreadPool在运行完线程后自动释放?
解答:在上述例子当中,我们创建的QRunnable类型的指针 QRunnable *task 是不需要我们手动去回收内存的,QThreadPool在结束该任务的执行后会将对该内存进行清空。
上述解答并不是凭空猜测,一方面根据是QT文档中的一句话:
QThreadPool takes ownership and deletes 'hello'automatically
用直白的话说就是:QThreadPool会占有这个指针的句柄并在运行结束后释放指针所占的内存。
另一方面,我们也通过改进上面的例子进行验证。
在程序运行过程中,我们观察发现程序的进程一直仅占用约1MB的内存空间。如果在main函数中所创建的100个HelloWorldTask 指针对象没有被QThreadPool释放的话,随着程序的运行该程序所占内存空间应该逐步攀升到约100MB。然而实际情况是,该程序最高仅占用1MB的内存空间。
综上两个方面可以得出以下结论:QRunnable创建的对象QThreadPool在执行完该对象后会帮助我们来清空内存,不需要我们手动回收内存。
第二部分:QThread 与QRunnable + QThreadPool适用的应用场景
QThread是QT的线程类,通过继承QThread然后重写run函数即可实现一个线程类。QThreadPool+ QRunnable配合构建线程池的方法也可以实现线程。我们通过以下问题对上述两种构建线程的方法进行分析和说明。
(1)既然QThread这么简单我们为什么还要使用QThreadPool + QRunnable创建线程池的方法来使用线程机制呢?
主要原因:当线程任务量非常大的时候,如果频繁的创建和释放QThread会带来比较大的内存开销,而线程池则可以有效避免该问题,相关的基础支持可以自行百度线程池的优点。
(2)QThread与 QThreadPool + QRunnable分别适用于哪种应用场景?
QThread适用于那些常驻内存的任务。而且QThread可以通过信号/槽的方式与外界进行通信。而QRunnable则适用于那些不常驻内存,任务数量比较多的情况。
第三部分:QRunnable 如何与外界进行通信
方法1:QRunnable并不继承自QObject类,因此无法使用信号/槽的方式与外界进行通信。我们就必须的使用其他方法,这里给大家介绍的是使用:QMetaObject::invokeMethod()函数。
方法2:使用多重继承的方法,任务子类同时继承自QRunnable和QObject。
【Qt开发】QThread中的互斥、读写锁、信号量、条件变量