文章目录
前言
本篇记录学习QT多线程的知识,参考视频13.1QThread创建多线程程序_哔哩哔哩。若涉及版权问题,请联系作者删除!
一、QThread创建多线程
1. 使用步骤:
- ①创建的类需要继承QThread类。
- ②重写run函数,新建的线程会执行run函数。
- ③线程开启:对象调用start方法,使线程执行run函数。
- ④线程终止:
- 对象调用terminate方法,提示线程不再执行run函数。
- 对象调用wait方法,等待线程执行完毕。
- ⑤线程销毁:动态申请new需要调用deleteLater方法销毁线程对象。(该函数可以放置于run函数内,当run函数执行完毕后就会销毁该线程对象,防止内存泄漏)注意:尽量少用静态申请栈空间的方式创建线程对象,因为很可能该对象销毁时线程仍在执行,就会报错。
**2. 案例演示:**创建一个类继承QThread,在重写的run方法中根据是否暂停来随机生成骰子值。
【1】实现效果:
【2】dicethread.h:
cpp
#ifndef DICETHREAD_H
#define DICETHREAD_H
#include <QThread>
class DiceThread : public QThread
{
Q_OBJECT
public:
DiceThread();
~DiceThread();
private:
void run() override;
signals:
void diceGenerate(int diceCount, int diceValue);
private slots:
void stopDiceThread_slot();
void startGenerate_slot();
void pauseGenerate_slot();
private:
int diceCount = 0; //第几次生成骰子
int diceValue = 1; //骰子的值
bool pauseFlag = true; //暂停生成骰子
bool stopFlag = true; //线程结束标志
};
#endif // DICETHREAD_H
【3】dicethread.cpp:
cpp
#include "dicethread.h"
#include <QRandomGenerator>
DiceThread::DiceThread()
{
}
DiceThread::~DiceThread()
{
}
/* 线程执行函数 */
void DiceThread::run()
{
stopFlag = false;
while (!stopFlag) {
if (!pauseFlag) {
diceValue = QRandomGenerator::global()->bounded(1, 7);
emit diceGenerate(++diceCount, diceValue);
}
msleep(500);
}
}
/* 接收到线程终止信号 */
void DiceThread::stopDiceThread_slot()
{
stopFlag = true;
}
/* 接收到开始生成信号 */
void DiceThread::startGenerate_slot()
{
pauseFlag = false;
}
/* 接收到停止生成信号 */
void DiceThread::pauseGenerate_slot()
{
pauseFlag = true;
}
【4】widget.h:
cpp
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "dicethread.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
signals:
void stopDiceThread(); //线程停止,DiceThread内部修改标志位
void startGenerate(); //开始生成,DiceThread内部修改标志位
void pauseGenerate(); //暂停生成,DiceThread内部修改标志位
private slots:
void on_btnStartThread_clicked();
void diceGenerate_slot(int, int);//接收DiceThread的骰子信息
void on_btnStopThread_clicked();
void on_btnDiceBegin_clicked();
void on_btnDiceEnd_clicked();
void on_btnClearText_clicked();
private:
Ui::Widget *ui;
DiceThread *dice = nullptr;
};
#endif // WIDGET_H
【5】widget.cpp:
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QString>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置按钮互斥
ui->btnStartThread->setEnabled(true);
ui->btnStopThread->setEnabled(false);
ui->btnDiceBegin->setEnabled(false);
ui->btnDiceEnd->setEnabled(false);
}
Widget::~Widget()
{
delete ui;
}
/* 槽函数:接收diceGenerate信号*/
void Widget::diceGenerate_slot(int diceCount, int diceValue)
{
ui->plainTextEdit->appendPlainText("第" + QString::number(diceCount) + "次,生成点数为:"
+ QString::number(diceValue));
}
/* "启动线程"按钮 */
void Widget::on_btnStartThread_clicked()
{
//创建DiceThread对象
dice = new DiceThread;
connect(dice, SIGNAL(diceGenerate(int, int)), this, SLOT(diceGenerate_slot(int, int)));
connect(this, SIGNAL(stopDiceThread()), dice, SLOT(stopDiceThread_slot()));
connect(this, SIGNAL(startGenerate()), dice, SLOT(startGenerate_slot()));
connect(this, SIGNAL(pauseGenerate()), dice, SLOT(pauseGenerate_slot()));
//启动线程
dice->start();
ui->plainTextEdit->appendPlainText("线程已启动");
//设置按钮互斥
ui->btnStartThread->setEnabled(false);
ui->btnStopThread->setEnabled(true);
ui->btnDiceBegin->setEnabled(true);
ui->btnDiceEnd->setEnabled(false);
}
/* "停止线程"按钮 */
void Widget::on_btnStopThread_clicked()
{
dice->terminate();
dice->wait();
dice->deleteLater();
ui->plainTextEdit->appendPlainText("线程已停止");
emit stopDiceThread();
//设置按钮互斥
ui->btnStartThread->setEnabled(true);
ui->btnStopThread->setEnabled(false);
ui->btnDiceBegin->setEnabled(false);
ui->btnDiceEnd->setEnabled(false);
}
/* "开始"按钮 */
void Widget::on_btnDiceBegin_clicked()
{
emit startGenerate();
ui->btnDiceBegin->setEnabled(false);
ui->btnDiceEnd->setEnabled(true);
}
/* "暂停"按钮 */
void Widget::on_btnDiceEnd_clicked()
{
emit pauseGenerate();
ui->btnDiceBegin->setEnabled(true);
ui->btnDiceEnd->setEnabled(false);
}
/* "清空"按钮 */
void Widget::on_btnClearText_clicked()
{
ui->plainTextEdit->clear();
}
二、QMutex基于互斥量的同步
**1. 背景:**当多个线程同时访问一块内存区域时,就会出现错误,因此需要线程同步。
**2. QMutex:**在互斥量之前上锁,然后在一个线程使用完互斥量之后解锁。如果一个线程试图向一个已经被其它线程上锁的互斥量上锁的话,这个线程将被阻塞,直到这个互斥量被解锁。
3. 基本使用:
- lock():上锁
- unlock():解锁
- tryLock():尝试上锁,若成功则上锁,不成功则返回false
4. 案例演示:
创建两个线程来同时访问value,通过查看相关调试信息来获取是哪个线程修改了value的值。我们是想让两个线程交叉着访问value。
【1】不加锁的代码:
cpp
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread();
void run() override;
void setNum(int *num);
private:
int *num;
};
#endif // MYTHREAD_H
cpp
#include "mythread.h"
MyThread::MyThread()
{
}
void MyThread::run()
{
while (*num > 0) {
(*num)--;
qDebug("线程号: %d, value = %d", QThread::currentThreadId(), *num);
}
}
void MyThread::setNum(int *num)
{
this->num = num;
}
cpp
#include "widget.h"
#include <QApplication>
#include "mythread.h"
int main(int argc, char *argv[])
{
int value = 20;
MyThread thread1, thread2, thread3;
thread1.setNum(&value);
thread2.setNum(&value);
thread3.setNum(&value);
thread1.start();
thread2.start();
thread3.start();
while (1) {}
return 0;
}
从运行结果中,我们能够看出value递减打印出现问题,这不是我们想要的。
【2】使用QMutex后:就不会出现上述的问题。
三、QReadWriteLock线程同步
**1. 说明:**使用互斥量时存在一个问题,每次只能有一个线程获得互斥量的权限。如果多个线程读取某个变量,就会出现排队现象。而实际上,有需求让多个线程同时读取。因此,引入QReadWriteLock实现线程同步。
2. 主要函数:
- lockForRead():只读方式锁定资源,如果有其他线程以写入方式锁定,这个函数会阻塞。
- lockForWrite():以写入方式锁定资源,如果其他线程以读或写模式锁定资源,则函数堵塞。
- unlock():解锁。
- tryLockForRead():是lockForRead的非阻塞版本。
- tryLockForWrite():是lockForWrite的非阻塞版本。
四、QWaitCondition线程同步
**1. 说明:**在著名的"生产者-消费者"模型中,生产者负责制作蛋糕,消费者负责拿走蛋糕。此时,生产者制作蛋糕完成后应该通知消费者,因此引入QWaiteCondition实现线程同步,需要搭配QMutex锁。
2. 常用函数:
- **wait(QMutex *lockedMutex):**进入等待状态,解锁互斥量lockedMutex,被唤醒后锁定lockedMutex并退出函数。
- **wakeAll():**唤醒所有处于等待状态的线程,唤醒顺序不确定。
- **wakeOne():**唤醒一个处于等待状态的线程,唤醒哪个不确定。
五、QSemaphore基于信号量的同步
**1. 说明:**信号量和互斥量的区别在于,信号量可以被多个线程同时访问,而互斥量只能被一个线程访问。同时,在"生产者-消费者"模型中,可能会有多个生产者生产蛋糕,生产者最多可以容纳10个蛋糕,然后消费者在蛋糕生产出来后就可以拿走。因此,引入QSemaphore实现基于信号量的线程同步。
2. 注意:
- QSemaphore 类中的信号量实际上是一个计数器,它用于记录能够同时访问某个共享资源的线程数。
3. 常用函数:
- **acquire(int n):**尝试获得n个资源,如果不够将堵塞线程,直到n个资源可用。调用后,信号量计数器-1.
- **release(int n):**释放资源,如果资源已经全部可用,则可扩充资源总数。调用后,信号量计数器+1.
- **int available():**返回当前信号量的资源个数。
- **bool tryAcquire(int n=1):**尝试获取n个资源,不成功时,不阻塞线程。