概述
在多线程应用程序中,由于多个线程的存在,线程之间可能需要访问同一个变量,或一个线程需要等待另外一个线程完成某个操作后才产生相应的动作,这时候就需要做线程同步。所以,需要线程同步情况:
1)多个线程之间访问同一个变量;
2)一个线程需要等待另外一个线程完成某个操作后才产生相应的动作。
问题引入:
示例1,使用了信号与槽机制,在产生新的骰子之后使用信号通知主线程读取新数据。如果不使用信号与槽,就需要主线程进行查询。
1、基于互斥量的线程同步
QMutex和QMutexLocker是基于互斥量的线程同步类,QMutex定义的实例是一个互斥量, QMulex主要提供3个函数。
•lock():锁定互斥量,如果另外一个线程锁定了这个互斥量,它将阻塞执行直到其他线程解锁这个互斥量。
•unlock():解锁一个互斥量,需要与lock()配对使用。
•tryLock():试图锁定一个互斥量,如果成功锁定就返回true;如果其他线程已经锁定了这 个互斥量,就返回false,但不阻塞程序执行。
定义的互斥量mutex相当于一个标牌,可以这样来理解互斥 :列车上的卫生间一次只能进 一个人,当一个人尝试进入卫生间就是lock(),如果有人占用,他就只能等待;等里面的人出来, 腾出了卫生间是unlock(),这个等待的人才可以进入并且锁住卫生间的门,就是lock(),使用完卫生间之后他再出来时就是unlock()。
使用互斥量,对QDiceThread类重新定义,不采用信号与槽机制,而是提供一个函数用于主线程读取数据。更改后的QDiceThread类定义增加如下:
#include <QThread>
#include <QMutex>
class QDiceThread : public QThread
{
Q_OBJECT
public:
QDiceThread();
bool readValue(int *seq, int *diceValue); // 用于主线程读取数据的函数
// ...
private:
QMutex mutex; // 互斥量
// ...
};
#endif // QDICE_THREAD_H
QDiceThread类里用QMutex类定义了一个互斥量变量mutex。
定义了函数readValue(),用于外部线程读取掷骰子的次数和点数,传递参数采用指针变量, 以便一次读取两个数据。
下面是QDiceThread类中关键的和readValue。函数的实现代码。
void QDiceThread::run()
{
qDebug()<< "QDiceThread run(), tid :" << QThread::currentThreadId();
//线程任务
m_stop=false;//启动线程时令m_stop=false
m_seq=0; //掷骰子次数
qsrand(QTime::currentTime().msec());//随机数初始化,qsrand是线程安全的
while(!m_stop)//循环主体
{
if (!m_Paused)
{
mutex.lock();
m_diceValue=qrand(); //获取随机数
m_diceValue=(m_diceValue % 6)+1;
m_seq++;
qDebug() << "produce new value:" << m_seq << m_diceValue
<< "tid :" << QThread::currentThreadId();;
mutex.unlock();
}
msleep(500); //线程休眠500ms
}
// 在 m_stop==true时结束线程任务
quit();//相当于 exit(0),退出线程的事件循环
}
在run()函数中,对重新计算骰子点数和掷骰子次数的3行代码用互斥量mutex的lock()和unlock()进行了保护,这部分代码的执行就不会被其他线程中断。注意lock()和unlock()必须配对使用。
在readValue()函数中,用互斥量mutex的tryLock()和unlock()进行了保护。如果tryLock()成功锁定互斥量,读取数值的两行代码执行时不会被中断,执行完后解锁;如果tryLock 锁定失败, 函数就立即返回,而不会等待。
注意:
原理上,对于两个或多个线程可能会同时读或写的变量应该使用互斥量进行保护,例如 QDiceThread中的变量m_stop和m_paused,在run()函数中读取这两个变量,要在diceBcgin()、diceEnd()和stopThread()函数里修改这些值,但是这3个函数都只有一条赋值语句,可以认为是原 子操作,所以,可以不用锁定保护。
主程序中,不断调用readValue()函数不断读取数值。这里使用定时器,用于定是主动读取投掷骰子线程的数值。
主线程处理类定义:
#include <QObject>
#include <QTimer>
#include "qdice_thread.h"
class Process: public QObject
{
Q_OBJECT
private:
QTimer mTimer; // 定时器
int mSeq, mDiceValue;
// ...
public slots:
// ...
void onTimeOut(); //定期器处理槽函数
};
#endif // PROCESS_H
主要是增加了一个定时器mTimer和其时间溢出响应槽函数onTimeOut(),在处理类的构造函 数中将mTimer的timeout信号与此槽函数关联。
connect(&mTimer, SIGNAL(timeout()), this, SLOT(onlimeOut()));
onTimeOut()函数的主要功能是调用threadA的readValue()函数读取数值。定时器的定时周期设置为100ms,小于threadA产生一次新数据的周期(500ms),所以可能读出旧的数据,通过存储的掷骰子的次数。读取的掷骰子次数是否不同,判断是否为新数据。onTimeOut()函数的代码如下:
// 定时器到时处理槽函数
void Process::onTimeOut()
{
qDebug()<< "Process onTimeOut(), tid :" << QThread::currentThreadId();
int tmpSeq=0, tmpValue=0;
bool valid = threadA.readValue(&tmpSeq, &tmpValue); // 读取数值
if (valid && (tmpSeq != mSeq)) // 有效,并且是新数据
{
mSeq = tmpSeq;
mDiceValue = tmpValue;
qDebug()<< "get new value :" << "mSeq=" << mSeq << ",mDiceValue=" << mDiceValue;
}
// usleep(1000);
}
启动线程时添加定时器周期设置:
mSeq=0;
mTimer.start(100); // 定时器100读取一次数据
线程结束时,增加定时器暂停:
mTimer.stop();
运行结果:
定时器消息延迟测试:
结论:当添加比较大的延迟时,定时器消息依然按固定时间执行。
QMuiex需要配对使用lock()和unlock()来实现代码段的保护,在一些逻辑复杂的代码段或可能发生异常的代码中,配对就可能出错。
QMutexLocker是另外一个简化了互斥量处理的类。QMutexLocker的构造函数接受一个互斥量作为参数并将其锁定,QMulcxLockcr的析构函数则将此互斥量解锁,所以在QMutexLocker实例变最的生存期内的代码段得到保护,自动进行互斥量的锁定和解锁。例如,QDiceThread的run() 函数的代码可以改写如下:
// 定义
#include <QMutex>
QMutex mutex; //互斥量
// 实现
void QDiceThread::run()
{
m_stop=false;//启动线程时令m_stop=false
m_seq=0;
qsrand(QTime::currentTime().msec());//随机数初始化,qsrand是线程安全的
while(!m_stop)//循环主体
{
if (!m_paused)
{
// mutex.lock();
QMutexLocker Locker(&mutex);
m_diceValue=qrand(); //获取随机数
m_diceValue=(m_diceValue % 6)+1;
m_seq++;
// mutex.unlock();
}
msleep(500); //线程休眠100ms
}
}
传送门:qt多线程系列文章目录
最后
以上就是畅快翅膀为你收集整理的qt 线程同步-互斥量(Qmutex)的全部内容,希望文章能够帮你解决qt 线程同步-互斥量(Qmutex)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复