Qt多线程编程(二)进阶篇

2021/6/20 20:29:48

本文主要是介绍Qt多线程编程(二)进阶篇,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录

一、前言

二、创建属于自己的线程

1、创建类文件 

2、定制线程功能

线程.h文件

线程.cpp文件 

main文件

主窗口.cpp文件

3、线程运行测试

时间不在下午三点

时间在下午三点

三、线程锁简介

1、QMutex类

2、QMutexLocker类

基本使用方法如下:

四、线程数据同步方式

1、加锁

2、信号量 QSemaphore

3、条件变量 QWaitConditon

4、共享内存 QsharedMemory

5、无锁原子操作 QAtomicInt,QAtomicInteger ,QAtomicPointer 

五、实战演练(待更新)

1、文件读写锁实现

2、生产消费锁的实现


 

一、前言

        经过 Qt 多线程编程(一)入门篇的学习,我们已经简单的熟悉了多线程以及与多线程相关的理论知识,剖析了 Qt 提供的 QThread 库的组成和使用方法。在掌握理论的同时,我们更应该注意实践能力,这篇文章就带大家进一步的深入学习 Qt 多线程的开发及应用。

二、创建属于自己的线程

1、创建类文件 

  • Step1:创建一个 Qt 项目,模板选用 Qt Widgets Application。

 

图 1 选取 Qt 模板
  • Step2:创建一个新的 Class

        在创建好的项目上方右键,弹出如下图所示的对话框,并选择 C++ Class。

图 2 新建文件

 

       接着填写类名和所需继承的基类,其中继承的基类和包含的文件可以根据需求自定义,这里全部不勾选,仅继承 QThread 基类。其他都选择下一步,最后点击完成即可完成文件的创建。

图 3 类定义

2、定制线程功能

        首先我们设置一个场景,我们在看视频(或工作),到下午三点时,触发了,三点几嘞,饮茶先啊这个事件。那么我们主线程就模拟看视频或工作,自定义的线程来进行判断是否到了三点钟,强制隐藏主线程界面,当小时数不等于三点钟时显示主界面。

线程.h文件

#ifndef CNEWTHREAD_H
#define CNEWTHREAD_H

#include <QThread>

class CNewThread : public QThread
{
    Q_OBJECT
public:
    CNewThread();
    ~CNewThread();
    void StartThread();
    void StopThread();
private:
    void run();

signals:
    void InformMainWnd(bool);

private:
    bool bIsRunning;
    QObject * m_parent;
};

#endif // CNEWTHREAD_H

线程.cpp文件 

#include "cnewthread.h"

#include <QDateTime>

CNewThread::CNewThread()
{
    bIsRunning = false;
}

CNewThread::~CNewThread()
{

}

void CNewThread::StartThread()
{
    bIsRunning = true;
    this->start();
}

void CNewThread::StopThread()
{
    bIsRunning = false;
    this->wait();
}

void CNewThread::run()
{
    static bool bIsPrompt = false;
    while (bIsRunning)
    {
        if(QTime::currentTime().hour() == 15 && !bIsPrompt)
        {
            bIsPrompt = true;
            emit InformMainWnd(true);
        }
        else if(QTime::currentTime().hour() != 15)
        {
            bIsPrompt = false;
            emit InformMainWnd(false);
        }
        sleep(5);
    }
}

main文件

#include "mainwindow.h"
#include "cnewthread.h"

#include <QApplication>
#include <QObject>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    CNewThread * pThread = new CNewThread();
    QObject::connect(pThread,SIGNAL(InformMainWnd(bool)),&w,SLOT(ProcessWnd(bool)));
    QObject::connect(pThread,SIGNAL(finished()),pThread,SLOT(deleteLater()));
    pThread->StartThread();
    w.show();

    int res = a.exec();
    pThread->StopThread();
    return res;
}

主窗口.cpp文件

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

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

void MainWindow::ProcessWnd(bool bIsHide)
{
    if(bIsHide)
    {
        QMessageBox::warning(this,"waring","It's three o'clock.Tea first!");
        this->hide();
    }
    else
        this->show();
}

3、线程运行测试

时间不在下午三点

图 4 线程测试——当前时间不处于下午三点

时间在下午三点

图 5 线程测试——当前时间处于下午三点

点击 OK 后主窗口将会隐藏,直到当前整点大于 15 点窗口将会继续显示。

三、线程锁简介

        项目开发过程中,某个进程可能有多个线程,当多个线程需要同时对一份内存数据进行操作时,为了保证线程安全,这时需要对进程进行加锁操作。加锁操作可以理解为牺牲时间来保证整个程序的稳定性能。

1、QMutex类

        Qt 提供了 QMutex 类来进行线程的加锁操作,官方文档地址:QMutex 。我们主要使用 lock 和 unlock 来完成线程的加解锁操作。

(1)void lock()      加锁

(2)void unlock()    解锁

        下面以 GUI 展示线程的加锁和解锁操作。

  • 设置三个线程工作,不加锁时的运行结果
图 6 线程不加锁操作同一个数据
  • 设置三个线程工作,加锁时的运行结果
图 7 线程加锁操作同一个数据

      由结果可以看出,不加锁时计算结果出现了错误。线程加锁后计算结果符合最终的结果。

2、QMutexLocker类

        在 QMutex 的基础上 Qt 内部还封装了 QMutexLocker 类,QMutexLocker 能够更好的完成自动加解锁的操作,它的底层实现是在构造函数中完成加锁,在析构函数中完成解锁,一旦 QMutexLocker 所在的函数完成,QMutexLocker 的生命周期也就结束,自动调用析构函数,此时锁自动释放。官方文档:QMutexLocker 。

基本使用方法如下:

int complexFunction(int flag)    //复杂的函数
{
    QMutexLocker locker(&mutex);   //创建QMutexLocker实例,加锁

    //可替换的内部函数 开始
    int retVal = 0;

    switch (flag) {
    case 0:
    case 1:
        return moreComplexFunction(flag);
    case 2:
        {
            int status = anotherFunction();
            if (status < 0)
                return -2;
            retVal = status + flag;
        }
        break;
    default:
        if (flag > 10)
            return -1;
        break;
    }
    
    return retVal;  //返回的同事销毁QMutexLocker实例,解锁
    //可替换的内部函数 结束
}

四、线程数据同步方式

1、加锁

        使用 QMutex 或 QMutexLocker 类完成。参考线程锁简介的内容。

2、信号量 QSemaphore

        信号量 QSemaphore 是互斥锁的泛化。互斥锁只能锁定一次,信号量则可以多次获取。信号量通常用于保护一定数量的相同资源。(一般用于典型的生产者线程和消费者线程)

信号量支持两种基本操作,acquire () 和 release ():

  • acquire( n ) 尝试获取n 个资源。如果没有那么多可用资源,调用将阻塞,直到出现这种情况。
  • release( n ) 释放n 个资源。

3、条件变量 QWaitConditon

       QWaitCondition 允许一个线程告诉其他线程某种条件已经满足。一个或多个线程可以阻塞等待 QWaitCondition 使用 wakeOne () 或 wakeAll ()设置条件。使用 wakeOne () 唤醒随机选择的一个线程或使用 wakeAll () 唤醒所有线程。

4、共享内存 QsharedMemory

        QSharedMemory 提供多个线程和进程对共享内存段的访问。它还为单个线程或进程提供了一种方法来锁定内存以进行独占访问。

        使用此类时,请注意以下平台差异:

  • Windows:QSharedMemory 不“拥有”共享内存段。当所有具有连接到特定共享内存段的 QSharedMemory 实例的线程或进程已销毁其 QSharedMemory 实例或退出时,Windows 内核会自动释放共享内存段。
  • Unix:QSharedMemory“拥有”共享内存段。当将 QSharedMemory 实例附加到特定共享内存段的最后一个线程或进程通过销毁其 QSharedMemory 实例与该段分离时,Unix 内核会释放共享内存段。但是如果最后一个线程或进程在没有运行 QSharedMemory 析构函数的情况下崩溃,共享内存段会在崩溃中幸存下来。
  • HP-UX:每个进程只允许一个附加到共享内存段。这意味着 QSharedMemory 不应跨 HP-UX 中同一进程中的多个线程使用。

        在对共享内存进行读写之前记得用 lock () 锁住共享内存,完成后记得用 unlock () 释放锁,与 QMutex 类似。

        当 QSharedMemory 的最后一个实例与段分离时,QSharedMemory 会自动销毁共享内存段,并且不会保留对该段的引用。

5、无锁原子操作 QAtomicInt,QAtomicInteger ,QAtomicPointer 

        所谓的原子操作,取的就是“原子是最小的、不可分割的最小个体”的意义,它表示在多个线程访问同一个共享资源的时候,能够确保所有其他的线程都不在同一时间内访问相同的资源。也就是他确保了在同一时刻只有唯一的线程对这个资源进行访问。这有点类似互斥对象对共享资源的访问的保护,但是原子操作更加接近底层,因而比使用互斥对象效率更高。

五、实战演练(待更新)

1、文件读写锁实现

2、生产消费锁的实现

 



这篇关于Qt多线程编程(二)进阶篇的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程