一、线程基础
1、GUI线程与工作线程
每个程序启动后拥有的第一个线程称为主线程,即GUI线程。QT中所有的组件类和几个相关的类只能工作在GUI线程,不能工作在次线程,次线程即工作线程,主要负责处理GUI线程卸下的工作。
2、数据的同步访问
每个线程都有自己的栈,因此每个线程都要自己的调用历史和本地变量。线程共享相同的地址空间。
二、QT多线程简介
QT通过三种形式提供了对线程的支持,分别是平台无关的线程类、线程安全的事件投递、跨线程的信号-槽连接。
QT中线程类包含如下:
QThread提供了跨平台的多线程解决方案
QThreadStorage提供逐线程数据存储
QMutex提供相互排斥的锁,或互斥量
QMutexLocker是一个辅助类,自动对QMutex加锁与解锁
QReadWriterLock提供了一个可以同时读操作的锁
QReadLocker与QWriteLocker自动对QReadWriteLock加锁与解锁
QSemaphore提供了一个整型信号量,是互斥量的泛化
QWaitCondition提供了一种方法,使得线程可以在被另外线程唤醒之前一直休眠。
三、QThread线程
1、QThread线程基础
QThread是Qt线程中有一个公共的抽象类,所有的线程类都是从QThread抽象类中派生的,需要实现QThread中的虚函数run(),通过start()函数来调用run函数。
void run()函数是线程体函数,用于定义线程的功能。
void start()函数是启动函数,用于将线程入口地址设置为run函数。
void terminate()函数用于强制结束线程,不保证数据完整性和资源释放。
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子类的类名)。
2、线程的优先级
QThread线程总共有8个优先级
QThread::IdlePriority0scheduled only when no other threads are running.
QThread::LowestPriority1scheduled less often than LowPriority.
QThread::LowPriority2scheduled less often than NormalPriority.
QThread::NormalPriority3the default priority of the operating system.
QThread::HighPriority4scheduled more often than NormalPriority.
QThread::HighestPriority5scheduled more often than HighPriority.
QThread::TimeCriticalPriority6scheduled as often as possible.
QThread::InheritPriority7use the same priority as the creating thread. This is the default.
void setPriority(Priority priority)
设置正在运行线程的优先级。如果线程没有运行,此函数不执行任何操作并立即返回。使用的start()来启动一个线程具有特定的优先级。优先级参数可以是QThread::Priority枚举除InheritPriortyd的任何值。
3、线程的创建
voidstart( Priority_priority_= InheritPriority )
启动线程执行,启动后会发出started()信号
4、线程的执行
int exec() [protected]
进入事件循环并等待直到调用exit(),返回值是通过调用exit()来获得,如果调用成功则返回0。
void run() [virtual protected]
线程的起点,在调用start()之后,新创建的线程就会调用run函数,默认实现调用exec(),大多数需要重新实现run函数,便于管理自己的线程。run函数返回时,线程的执行将结束。
5、线程的退出
void quit();
通知线程事件循环退出,返回0表示成功,相当于调用了QThread::exit(0)。
void exit( intreturnCode= 0 );
调用exit后,thread将退出event loop,并从exec返回,exec的返回值就是returnCode。通常returnCode=0表示成功,其他值表示失败。
void terminate();
结束线程,线程是否立即终止取决于操作系统。
线程被终止时,所有等待该线程Finished的线程都将被唤醒。
terminate是否调用取决于setTerminationEnabled( boolenabled= true )开关。
void requestInterruption()
请求线程的中断。请求是咨询意见并且取决于线程上运行的代码,来决定是否及如何执行这样的请求。此函数不停止线程上运行的任何事件循环,并且在任何情况下都不会终止它。
工程中线程退出的解决方案如下:
通过在线程类中增加标识变量volatile bool m_stop,通过m_stop变量的值判断run函数是否执行结束返回。
#ifndef WORKTHREAD_H
#define WORKTHREAD_H
#include <QThread>
#include <QDebug>
class WorkThread : public QThread
{
protected:
//线程退出的标识量
volatile bool m_stop;
void run()
{
qDebug() << "run begin";
while(!m_stop)
{
//task handling
int* p = new int[1000];
for(int i = 0; i < 1000; i++)
{
p[i] = i * i;
}
sleep(2);
delete [] p;
}
qDebug() << "run end";
}
public:
WorkThread()
{
m_stop = false;
}
//线程退出的接口函数,用户使用
void stop()
{
m_stop = true;
}
};
#endif // WORKTHREAD_H
6、线程的等待
bool wait( unsigned longtime= ULONG_MAX )
线程将会被阻塞,等待time毫秒,如果线程退出,则wait会返回。Wait函数解决多线程在执行时序上的依赖。
void msleep( unsigned longmsecs)
void sleep( unsigned longsecs)
void usleep( unsigned longusecs)
sleep()、msleep()、usleep()允许秒,毫秒和微秒来区分,但在Qt5.0中被设为public。
一般情况下,wait()和sleep()函数应该不需要,因为Qt是一个事件驱动型框架。考虑监听finished()信号来取代wait(),使用QTimer来取代sleep()。
7、线程的状态
bool isFinished() const线程是否已经退出
bool isRunning() const线程是否处于运行状态
8、线程的属性
Prioritypriority() const
void setPriority( Prioritypriority)
uint stackSize() const
void setStackSize( uintstackSize)
void setTerminationEnabled( boolenabled= true )
设置是否响应terminate()函数
9、线程与事件循环
QThread中run()的默认实现调用了exec(),从而创建一个QEventLoop对象,由QEventLoop对象处理线程中事件队列(每一个线程都有一个属于自己的事件队列)中的事件。exec()在其内部不断做着循环遍历事件队列的工作,调用QThread的quit()或exit()方法使退出线程,尽量不要使用terminate()退出线程,terminate()退出线程过于粗暴,造成资源不能释放,甚至互斥锁还处于加锁状态。
线程中的事件循环,使得线程可以使用那些需要事件循环的非GUI 类(如,QTimer,QTcpSocket,QProcess)。
在QApplication前创建的对象,QObject::thread()返回NULL,意味着主线程仅为这些对象处理投递事件,不会为没有所属线程的对象处理另外的事件。可以用QObject::moveToThread()来改变对象及其子对象的线程亲缘关系,假如对象有父亲,不能移动这种关系。在另一个线程(而不是创建它的线程)中deleteQObject对象是不安全的。除非可以保证在同一时刻对象不在处理事件。可以用QObject::deleteLater(),它会投递一个DeferredDelete事件,这会被对象线程的事件循环最终选取到。假如没有事件循环运行,事件不会分发给对象。假如在一个线程中创建了一个QTimer对象,但从没有调用过exec(),那么QTimer就不会发射它的timeout()信号,deleteLater()也不会工作。可以手工使用线程安全的函数QCoreApplication::postEvent(),在任何时候,给任何线程中的任何对象投递一个事件,事件会在那个创建了对象的线程中通过事件循环派发。事件过滤器在所有线程中也被支持,不过它限定被监视对象与监视对象生存在同一线程中。QCoreApplication::sendEvent(不是postEvent()),仅用于在调用此函数的线程中向目标对象投递事件。
四、线程的同步
1、线程同步基础
临界资源:每次只允许一个线程进行访问的资源
线程间互斥:多个线程在同一时刻都需要访问临界资源
线程锁能够保证临界资源的安全性,通常,每个临界资源需要一个线程锁进行保护。
线程死锁:线程间相互等待临界资源而造成彼此无法继续执行。
产生死锁的条件: