十三、Qt多线程与线程安全

一、多线程程序

QThread类提供了管理线程的方法:
  • 一个对象管理一个线程
  • 一般从QThread继承一个自定义类,重载run函数

1、实现程序

(1)创建项目,基于QDialog

(2)添加类,修改基于QThread

bash 复制代码
#ifndef DICETHREAD_H
#define DICETHREAD_H

#include <QThread>

class DiceThread : public QThread
{
    Q_OBJECT

private:
    int m_seq = 0;
    int m_diceValue;
    bool m_Paused = true;
    bool m_stop = false;

public:
    explicit DiceThread();

    void diceBegin();
    void dicePause();
    void stopThread();

protected:
    void run() Q_DECL_OVERRIDE;

signals:
    void newValued(int seq, int diceValue);

public slots:
};

#endif // DICETHREAD_H
bash 复制代码
#include "dicethread.h"
#include <QTime>

DiceThread::DiceThread()
{

}

void DiceThread::diceBegin()
{
    m_Paused = false;
}

void DiceThread::dicePause()
{
    m_Paused = true;
}

void DiceThread::stopThread()
{
    m_stop = true;
}

void DiceThread::run()
{
    m_stop = false;
    m_seq = 0;
    qsrand(QTime::currentTime().second());
    while (!m_stop) {
        if(!m_Paused)
        {
            m_diceValue = qrand()%6+1;
            m_seq++;
            emit newValued(m_seq, m_diceValue);
        }
        sleep(1);
    }
    quit();
}

(3)实现按钮功能

bash 复制代码
#include "dialog.h"
#include "ui_dialog.h"

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);

    ui->btnStartThread->setEnabled(true);
    ui->btnStart->setEnabled(false);
    ui->btnStop->setEnabled(false);
    ui->btnStopThread->setEnabled(false);

    connect(&threadA, SIGNAL(started()),
            this, SLOT(on_threadAStarted()));
    connect(&threadA, SIGNAL(finished()),
            this, SLOT(on_threadAFinished()));
    connect(&threadA, SIGNAL(newValued(int,int)),
            this, SLOT(on_threadAnewValue(int,int)));
}

Dialog::~Dialog()
{
    delete ui;
}

void Dialog::closeEvent(QCloseEvent *event)
{
    if(threadA.isRunning())
    {
        threadA.stopThread();
        threadA.wait();
    }
    event->accept();
}

void Dialog::on_btnStartThread_clicked()
{
    threadA.start();
}

void Dialog::on_btnStart_clicked()
{
    threadA.diceBegin();
}

void Dialog::on_btnStop_clicked()
{
    threadA.dicePause();
}

void Dialog::on_btnStopThread_clicked()
{
    threadA.stopThread();
}

void Dialog::on_btnClearText_clicked()
{
    ui->plainTextEdit->clear();
}

void Dialog::on_threadAnewValue(int seq, int diceValue)
{
    ui->plainTextEdit->appendPlainText(QString::asprintf("第%d次投色子: 点数%d", seq, diceValue));
}

void Dialog::on_threadAStarted()
{
    ui->labelStatus->setText("Thread状态:started");
    ui->btnStartThread->setEnabled(false);
    ui->btnStart->setEnabled(true);
    ui->btnStop->setEnabled(true);
    ui->btnStopThread->setEnabled(true);
}

void Dialog::on_threadAFinished()
{
    ui->labelStatus->setText("Thread状态:finished");
    ui->btnStartThread->setEnabled(true);
    ui->btnStart->setEnabled(false);
    ui->btnStop->setEnabled(false);
    ui->btnStopThread->setEnabled(false);
}

二、互斥量

QMutex和QMutexLocker是基于互斥量的线程同步类
  • QMutex定义的实力是互斥量,主要提供了三个函数
    • lock():锁定互斥量,如果另一个线程锁定了这个互斥量,将阻塞直到另一个解锁
    • unlock():解锁一个互斥量
    • trylock():尝试锁定一个互斥量,如果成功返回true,失败(其他线程已经锁定这个互斥量)返回false,不阻塞线程。
  • QMutexLocker简化了互斥量的处理
    • 构造一个函数接受一个互斥量作为参数,并将其锁定
    • 析构函数解锁该互斥量

1、实现程序

(1)拷贝上一个项目

(2)修改程序为直接读取

bash 复制代码
void DiceThread::readValue(int *seq, int *diceValue)
{
    *seq = m_seq;
    *diceValue = m_diceValue;
}

void DiceThread::run()
{
    m_stop = false;
    m_seq = 0;
    qsrand(QTime::currentTime().second());
    while (!m_stop) {
        if(!m_Paused)
        {
            m_diceValue = 50;
            msleep(50);
            m_diceValue = qrand();
            msleep(50);
            m_diceValue = m_diceValue%6+1;
            msleep(50);
            m_seq++;
//            emit newValued(m_seq, m_diceValue);
        }
        sleep(1);
    }
    quit();
}
bash 复制代码
void Dialog::on_TimerOut()
{
    int seq, diceValue;
    threadA.readValue(&seq, &diceValue);
    ui->plainTextEdit->appendPlainText(QString::asprintf("第%d次投色子: 点数%d", seq, diceValue));
}

(3)使用QMutex互斥量

bash 复制代码
void DiceThread::readValue(int *seq, int *diceValue)
{
    mMutex.lock();
    *seq = m_seq;
    *diceValue = m_diceValue;
    mMutex.unlock();
}

void DiceThread::run()
{
    m_stop = false;
    m_seq = 0;
    qsrand(QTime::currentTime().second());
    while (!m_stop)
    {
        if(!m_Paused)
        {
            mMutex.lock();
            m_diceValue = 50;
            msleep(50);
            m_diceValue = qrand();
            msleep(50);
            m_diceValue = m_diceValue % 6 + 1;
            msleep(50);
            m_seq++;
            //            emit newValued(m_seq, m_diceValue);
            mMutex.unlock();
        }
        sleep(1);
    }
    quit();
}

(4)使用QMutexLocker

bash 复制代码
void DiceThread::readValue(int *seq, int *diceValue)
{
    QMutexLocker locker(&mMutex);
    *seq = m_seq;
    *diceValue = m_diceValue;
}

(5)使用QMutex.trylock

bash 复制代码
bool DiceThread::readValue(int *seq, int *diceValue)
{
    //    QMutexLocker locker(&mMutex);
    if(mMutex.tryLock())
    {
        *seq = m_seq;
        *diceValue = m_diceValue;
        mMutex.unlock();
        return true;
    }
    return false;
}

三、读写锁

QReadWriteLock提供了以下主要函数:
  • lockForRead():只读方式锁定资源,如果有其他线程以写入方式锁定,这个函数会阻塞
  • lockForWrite():以写入方式锁定资源,如果本线程或者其他线程以读取或写入锁定资源,则函数阻塞
  • unlock():解锁
  • tryLockForRead():是lockForRead非阻塞版本
  • tryLockForWrite():是lockForWrite非阻塞版本
  • 读写锁同样有QReadLocker和QWriteLocker

四、条件变量QWaitCondition

QWaitCondition用于通知其他线程,如接收数据和处理数据之间通知。提供了一些函数:
  • wait(QMutex *lockedMutex):进入等待状态,解锁互斥量lockMutex,被唤醒后锁定lockMutex并退出函数
  • wakeAll():唤醒所有处于等待的线程,线程唤醒的顺序不确定,有操作系统调度策略决定
  • QakeOne():唤醒一个处于等待状态的线程,唤醒哪个线程不确定,由操作系统调度策略决定

1、实现程序

(1)拷贝上一个项目

(2)使用QWaitCondition设置数据更新

bash 复制代码
#include "dicethread.h"
#include <QTime>
#include <QWaitCondition>
#include <QMutex>

int m_seq = 0;
int m_diceValue;
bool m_stop = false;
QMutex m_Mutex;
QWaitCondition waitCondition;

ProducerThread::ProducerThread()
{

}

void ProducerThread::stopThread()
{
    m_stop = true;
}

void ProducerThread::run()
{
    m_stop = false;
    m_seq = 0;
    qsrand(QTime::currentTime().second());
    while (!m_stop)
    {
        m_Mutex.lock();
        m_diceValue = qrand() % 6 + 1;
        m_seq++;
        m_Mutex.unlock();
        waitCondition.wakeOne();
        sleep(1);
    }
    quit();
}

ConsumerThread::ConsumerThread()
{

}

void ConsumerThread::stopThread()
{
    m_stop = true;
    waitCondition.wakeOne(); // 需要给wait置信号,否则阻塞无法结束
}

void ConsumerThread::run()
{
    m_stop = false;
    while (!m_stop)
    {
        m_Mutex.lock();
        waitCondition.wait(&m_Mutex);
        emit newValued(m_seq, m_diceValue);
        m_Mutex.unlock();
        msleep(100);
    }
    quit();
}

五、信号量

QSemaphore信号量通常用于保护一定数量的相同的资源。QSemaphore是实现信号量功能的类,提供了以下函数:
  • acquire(int n):尝试获得n个资源,如果不够将阻塞线程,直到n个资源可用
  • release(int n):释放资源,如果资源已经全部可用,则可扩充资源总数
  • int available():返回房前信号量的资源个数
  • bool tryAcquire(int n=1):尝试获取n个资源,不成功是,不阻塞线程

1、实现程序

(1)创建项目,基于QDIalog

(2)创建线程类

(3)使用信号量实现功能

bash 复制代码
#include "threadtest.h"
#include <QSemaphore>

const int bufferSize = 8;
int buffer1[bufferSize] = {0};
int buffer2[bufferSize] = {0};
int curBuf = 1; // 当前采集数据使用的缓冲区

QSemaphore semEmptyBufs(2); // 两个资源
QSemaphore semFullBufs;

ThreadDAQ::ThreadDAQ()
{

}

void ThreadDAQ::stopThread()
{
    m_stop = true;
}

void ThreadDAQ::run()
{
    m_stop = false;
    int counter = 0;
    while(!m_stop)
    {
        semEmptyBufs.acquire();
        for (int i = 0; i < bufferSize; ++i)
        {
            if(curBuf == 1)
            {
                buffer1[i] = counter;
            }
            else
            {
                buffer2[i] = counter;
            }
            counter++;
            msleep(50);
        }
        if(curBuf == 1)
        {
            curBuf = 2;
        }
        else
        {
            curBuf = 1;
        }
        semFullBufs.release();
    }
    exit();
}

ThreadShow::ThreadShow()
{

}

void ThreadShow::stopThread()
{
    m_stop = true;
}

void ThreadShow::run()
{
    m_stop = false;
    int seq = 0;
    while(!m_stop)
    {
        semFullBufs.acquire();
        int buf[bufferSize] = {0};
        if(curBuf == 1)
        {
            memcpy(buf, buffer2, sizeof(int)*bufferSize);
        }
        else
        {
            memcpy(buf, buffer1, sizeof(int)*bufferSize);
        }
        emit newValue(buf, bufferSize, seq++);
        semEmptyBufs.release();
    }
    exit();
}
bash 复制代码
#include "dialog.h"
#include "ui_dialog.h"


Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);

    ui->btnStopThread->setEnabled(false);

    connect(&threadConsumer, SIGNAL(newValue(int*, int, int)),
            this, SLOT(on_threadNewValue(int*, int, int)));

    connect(&threadProducer, SIGNAL(started()),
            this, SLOT(on_threadProducer_started()));
    connect(&threadProducer, SIGNAL(finished()),
            this, SLOT(on_threadProducer_finished()));
    connect(&threadConsumer, SIGNAL(started()),
            this, SLOT(on_threadConsumer_started()));
    connect(&threadConsumer, SIGNAL(finished()),
            this, SLOT(on_threadConsumer_finished()));
}

Dialog::~Dialog()
{
    delete ui;
}

void Dialog::on_threadNewValue(int *data, int count, int seq)
{
    QString str = QString::asprintf("第%03d次,内容:", seq);
    for (int var = 0; var < count; ++var)
    {
        str += QString::asprintf("%03d ,", data[var]);
    }

    ui->plainTextEdit->appendPlainText(str);
}

void Dialog::on_btnStartThread_clicked()
{
    threadConsumer.start();
    threadProducer.start();

    ui->btnStartThread->setEnabled(false);
    ui->btnStopThread->setEnabled(true);
}

void Dialog::on_btnStopThread_clicked()
{
    threadProducer.stopThread();
    threadConsumer.stopThread();

    ui->btnStartThread->setEnabled(true);
    ui->btnStopThread->setEnabled(false);
}

void Dialog::on_btnClearText_clicked()
{
    ui->plainTextEdit->clear();
}

void Dialog::on_threadProducer_started()
{
    ui->labelProducer->setText("Producer线程:started");
}

void Dialog::on_threadProducer_finished()
{
    ui->labelProducer->setText("Producer线程:finished");
}

void Dialog::on_threadConsumer_started()
{
    ui->labelConsumer->setText("Consumer线程:started");
}

void Dialog::on_threadConsumer_finished()
{
    ui->labelConsumer->setText("Consumer线程:finished");
}
相关推荐
优雅的小武先生3 小时前
QT中的按钮控件和comboBox控件和spinBox控件无法点击的bug
开发语言·qt·bug
Death2003 小时前
使用Qt进行TCP和UDP网络编程
网络·c++·qt·tcp/ip
海绵波波1074 小时前
Qt操作主/从视图及XML——实例:汽车管理系统
xml·qt·汽车
Fairy_sevenseven5 小时前
【二十八】【QT开发应用】模拟WPS Tab
开发语言·qt·wps
Death2005 小时前
Qt 中的 QListWidget、QTreeWidget 和 QTableWidget:简化的数据展示控件
c语言·开发语言·c++·qt·c#
niu_sama5 小时前
基于muduo库函数实现protobuf协议的通信
开发语言·qt
不写八个5 小时前
Qt教程(001):Qt概述与安装
开发语言·qt
Death2006 小时前
Qt 3D、QtQuick、QtQuick 3D 和 QML 的关系
c语言·c++·qt·3d·c#
一律清风16 小时前
QT-文件创建时间修改器
c++·qt
不知所云,16 小时前
qt cmake自定义资源目录,手动加载资源(图片, qss文件)
开发语言·qt