在QT中实现线程暂停
目录
一、实现方法
1)互斥锁实现暂停
2)条件等待类实现
二、总结
QT本身并没有提供实现线程暂停的机制,可是在某些情况下,我们可能需要暂停线程的执行,暂停线程可能是用户手动暂停,也可能是在某些条件下暂停,且在暂停后可以随时继续线程的运行。下面介绍如何用QT实现线程暂停和继续。
一、实现方法
在QT中并没有定义可以直接使线程暂停的函数或相关功能,通常清空下线程只有在完成所有工作后才会退出。若要在线程正在运行的过程中实现暂停,可以借用QT中的互斥锁来实现,或者使用条件等待两种方式。
1)互斥锁实现暂停
(1)实现思路
互斥锁即使用QMutex类。QMutex通常用在线程同步中,也可借助QMutex类的特性实现线程暂停。首先定义一个QMutex对象mutex,mutex可被主线程和子线程访问,或多个线程访问。我们知道mutex只能同时被锁定一次。比如如果主线程调用lock()函数锁定了mutex,那么子线程再调用lock()锁定mutex时,子线程将会被阻塞,直到主线程调用unlock()解锁mutex后,子线程才会解除阻塞继续执行。因此,借用QMutex对象的锁定机制,我们就可以实现子线程的暂停和继续功能。
(2)代码示例
首先我们在头文件中定义了一个QThread对象,在该类中申明了一个QMutex myLock锁,这个锁为public类型,便于被外部访问。
class PlayThread : public QThread
{Q_OBJECT
public:QMutex myLock;//定义一个互斥锁protected:void run();
};
子线程的实现代码如下,我们定义了一个for循环,具体的业务代码在这个循环中实现。在业务逻辑的先后我们分别执行了对myLock的上锁和解锁过程。myLock.lock()用于上锁,也就是用于阻塞线程实现暂停的目的。在一次业务逻辑完成后执行myLock.unlock()解锁是非常有必要的,因为前面子线调用lock对该锁执行了上锁,那么子线程在业务执行完成后就必须要解锁,否则在子线的外部的主线程或其它线程将不能对myLock进行上锁,也就不能再实现暂停的功能,而且会导致外部线程被阻塞。所以,在此处子线程的业务完成后必须解锁。其实,为了更可靠的防止外部线程被阻塞,子线程可以在上锁后立即解锁,而不用等待业务执行完毕。
void PlayThread::run()
{for(courrentCount = primaryFrame; courrentCount < frameCount; courrentCount++){myLock.lock();readData();//读取数据myLock.unlock();}if(courrentCount == frameCount){primaryFrame = 0;courrentCount = 0;emit threadEnd();//发送线程结束信号}
}
2)条件等待类实现
(1)实现思路
第二种实现方法是使用QWaitCondition类。QWaitCondition的核心是提供了两个方法,wait()和wakeOne()/wakeAll()。一个用于阻塞线程,一个用于唤醒线程,两个方法结合使用就可以实现线程的暂停和继续。首先我们需要定义一个QWaitCondition对象,该对象同样要被主线程和子线程访问,或多个线程访问。在被需要暂停的线程中我们调用wait()方法,用于阻塞线程,在其它线程中我们调用wake方法用于唤醒线程。
wait()方法有两个重构方式,常用的是wait(QMutex *lockedMutex, unsigned long time = ULONG_MAX)模式,两个参数分别是QMutex 和unsigned long类型。ULONG_MAX表示的是等待超时时间,默认情况下会永远等待。lockedMutex是一个互斥锁,在调用wait()函数时,会解锁该互斥锁。因此,在调用wait()函数前,我们必须要对lockedMutex进行上锁,而且上锁必须在该子线程中进行。
唤醒方法同样有两个,wakeOne()和wakeAll()。QWaitCondition对象的wait()函数可以在多个子线程中被使用,wakeAll()顾名思义,唤醒所有调用了wait()函数的子线程,唤醒顺序是由操作系统来决定的,不可控。wakeOne()是唤醒一个子线程,如果存在多个子线程调用了QWaitCondition对象的wait()函数,那么唤醒的是哪一个子线程也是不可控的,因此要实现唤醒指定的子线程,目前只能通过为特定的子线程定义单独的QWaitCondition对象,以对象名称区分来实现。
(2)代码示例
以下是子线程中的实现代码,mutex是一个互斥锁对象,首先在循环中对其进行上锁。keyPressed是一个QWaitCondition对象,上锁后即进入等待状态。在其它线程中调用keyPressed.wakeAll()或keyPressed。wakeOne()后即可唤醒该子线程,并继续执行后续业务工作。
forever {mutex.lock();keyPressed.wait(&mutex);do_something();mutex.unlock();}
二、总结
以上两种实现方案是QT中线程暂停的常用方法。其中通过条件等待类实现的方法形式相对比较简单,也易于理解,但是其不具备随时暂停的功能,子线程在每完成一次业务逻辑后均会进入阻塞状态,直到被唤醒,因此其灵活性不够,使用范围受限。第一种方法直接利用了互斥锁的阻塞机制,可随时暂停和继续,使用更加灵活。但是如果线程之间的关系把握不清的话容易超出内存访问冲突或死锁的情况,因此在使用时要准确分析业务逻辑。在大多数情况下,为了实现灵活性,更建议使用第一种方法。