多线程这个东西越接触越觉得他的强大,我高中的时候就希望自己有几个脑袋,一个看电视,一个玩,一个写作业,一心多用,这个多线程不正好就是的,本次主要是对基于互斥锁的线程同步的研究,显示最基本的互斥锁。
首先是互斥锁的概念,他是来源于生产者/消费者(producer/consumer)模型,比如有一个分线程是负责生产,主线程是消费者,从分线程获取资源,互斥锁 是为了让分线程生产好了一个东西以后再让主线程来获取。
互斥锁有两个类QMutex和QMutexLocker,其中QMutex的实例就是互斥量。QMutex常用的函数有:
lock() | 锁定互斥量 |
unlock() | 解锁一个互斥量 |
trylock() | 尝试锁一个互斥量,不行就算了 |
我个人觉得这就跟指针一样,自己上锁解锁比较容易出错或者忘了,可以像智能指针一样,让他完成以后自己解锁,这就是QMutexLocker的作用。把互斥量作为参数传给QMutexLocker就行了,系统会在分线程完成一次生产后让主线程接受。
读写锁和等待
接下来就是性能优化的问题了这里提出了读写锁的概念,互斥量本身其实是影响程序的性能的,他在生产的过程中会造成堵塞,其他的线程都要等着,应该允许让多个线程访问互斥量。
LockForRead() | 以只读的方式锁定资源 |
LockForWrite() | 以只写的方式锁定资源 |
unlock() | 解锁 |
tryLockForRead() | LockForRead()的非阻塞版本 |
tryLockForWrite() | LockForWrite()的非阻塞版本 |
QReadLocker和QWriteLocker和前面互斥量的QMutexLocker一样,绑定以后不需要手动解锁上锁操作。
基于互斥量的线程同步有几个问题:
第一个问题就是一个线程资源释放了以后,不能及时的通知其他线程
这里引入了QWaitCondition,他的主要函数:
wait(QMutex *lockedmutex) | 进入等待状态,解锁互斥量lockedmutex,被唤醒后锁定lockedmutex并退出函数 |
wakeAll() | 唤醒所有处于等待的线程,唤醒顺序不确定,这个看系统的调度策略 |
wakeOne() | 唤醒一个处于等待的线程,唤醒哪一个不确定,这个看系统的调度策略 |
QWaitCondition一般用于生产者/消费者模型,前面说过了,模型视图如图:
(线程之间的通讯和之前一样采用信号槽的方式)
实例这里我自己没写,就看看书上的源码(Qt6 C++开发指南)
参考上面的模型做的,创建了3个线程,一个生产者TDiceThread,两个消费者TValueThread(获取点数)和TPictureThread(根据获取点数生成图片文件名)
有两个全局变量seq(步数)和dicevalue(点数)
int seq=0, diceValue=0;//seq步数,dicevalue点数
定义实现3个上述的线程
//TDiceThread 是产生骰子点数的线程
class TDiceThread : public QThread
{
Q_OBJECT
protected:
void run(); //线程的任务函数
public:
explicit TDiceThread(QObject *parent = nullptr);
};
//TValueThread 获取骰子点数
class TValueThread : public QThread
{
Q_OBJECT
protected:
void run(); //线程的任务函数
public:
explicit TValueThread(QObject *parent = nullptr);
signals:
void newValue(int seq, int diceValue);
};
//TPictureThread获取骰子点数,生成对应的图片文件名
class TPictureThread : public QThread
{
Q_OBJECT
protected:
void run(); //线程的任务函数
public:
explicit TPictureThread(QObject *parent = nullptr);
signals:
void newPicture(QString &picName);
};
重写run函数:
void TDiceThread::run()//生产者的run函数重写
{//线程的任务函数
seq=0;
while(1)
{
rwLocker.lockForWrite(); //以写方式锁定
diceValue = QRandomGenerator::global()->bounded(1,7); //产生随机数[1,6]
seq++;
rwLocker.unlock(); //解锁
waiter.wakeAll(); //唤醒其他等待的线程
msleep(500); //线程休眠500ms
}
}
void TValueThread::run()//获取点数的run函数重写
{
while(1)
{
rwLocker.lockForRead(); //以只读方式锁定
waiter.wait(&rwLocker); //等待被唤醒
emit newValue(seq,diceValue);
rwLocker.unlock(); //解锁
}
}
void TPictureThread::run()//通过获取点数生成对应图片文件名的run函数重写
{
while(1)
{
rwLocker.lockForRead(); //以只读方式锁定
waiter.wait(&rwLocker); //等待被唤醒
QString filename=QString::asprintf(":/dice/images/d%d.jpg",diceValue);
emit newPicture(filename);
rwLocker.unlock(); //解锁
}
}
创建线程并挂到对象树上:
mainwindow.h
private:
TDiceThread *threadA; //producer
TValueThread *threadValue; //consumer 1
TPictureThread *threadPic; //consumer 2
mainwindow.cpp
threadA= new TDiceThread(this); //producer
threadValue= new TValueThread(this); //consumer 1
threadPic= new TPictureThread(this); //consumer 2
线程之间使用信号槽机制通讯:
mainwindow.h
private slots:
void do_threadA_started();
void do_threadA_finished();
void do_newValue(int seq, int diceValue);
void do_newPicture(QString &picName);
mainwindow.cpp
connect(threadA,&TDiceThread::started, this, &MainWindow::do_threadA_started);
connect(threadA,&TDiceThread::finished,this, &MainWindow::do_threadA_finished);
connect(threadValue,&TValueThread::newValue,this, &MainWindow::do_newValue);
connect(threadPic,&TPictureThread::newPicture,this, &MainWindow::do_newPicture);
void MainWindow::do_threadA_started()
{//与线程的started()信号关联
ui->statusbar->showMessage("Thread状态:thread started");
ui->actThread_Run->setEnabled(false);
ui->actThread_Quit->setEnabled(true);
}
void MainWindow::do_threadA_finished()
{//与线程的finished()信号关联
ui->statusbar->showMessage("Thread状态:thread finished");
ui->actThread_Run->setEnabled(true);
ui->actThread_Quit->setEnabled(false);
}
void MainWindow::do_newValue(int seq, int diceValue)
{
QString str=QString::asprintf("第 %d 次掷骰子,点数为:%d",seq,diceValue);
ui->plainTextEdit->appendPlainText(str);
}
void MainWindow::do_newPicture(QString &picName)
{
QPixmap pic(picName);
ui->labPic->setPixmap(pic);
}
另外为了避免线程还没结束就关闭程序,要重写一下wediget的closeevent函数:
void MainWindow::closeEvent(QCloseEvent *event)
{
if (threadA->isRunning())
{
threadA->terminate(); //强制结束线程
threadA->wait(); //等待线程结束
}
if (threadValue->isRunning())
{
threadValue->terminate(); //强制结束线程
threadValue->wait(); //等待线程结束
}
if (threadPic->isRunning())
{
threadPic->terminate(); //强制结束线程
threadPic->wait(); //等待线程结束
}
event->accept();
}
关闭窗口的时候检查三个线程是不是还在运行,如果在运行就强制关闭。
这样程序大体就完成了,详见源程序
标签:do,QT,threadA,void,互斥,线程,seq From: https://blog.csdn.net/Excalibur6/article/details/144886141