目录
一、前言
二、创建属于自己的线程
1、创建类文件
2、定制线程功能
线程.h文件
线程.cpp文件
main文件
主窗口.cpp文件
3、线程运行测试
时间不在下午三点
时间在下午三点
三、线程锁简介
1、QMutex类
2、QMutexLocker类
基本使用方法如下:
四、线程数据同步方式
1、加锁
2、信号量 QSemaphore
3、条件变量 QWaitConditon
5、无锁原子操作 QAtomicInt,QAtomicInteger ,QAtomicPointer
五、实战演练(待更新)
1、文件读写锁实现
2、生产消费锁的实现
经过 Qt 多线程编程(一)入门篇的学习,我们已经简单的熟悉了多线程以及与多线程相关的理论知识,剖析了 Qt 提供的 QThread 库的组成和使用方法。在掌握理论的同时,我们更应该注意实践能力,这篇文章就带大家进一步的深入学习 Qt 多线程的开发及应用。
在创建好的项目上方右键,弹出如下图所示的对话框,并选择 C++ Class。
接着填写类名和所需继承的基类,其中继承的基类和包含的文件可以根据需求自定义,这里全部不勾选,仅继承 QThread 基类。其他都选择下一步,最后点击完成即可完成文件的创建。
首先我们设置一个场景,我们在看视频(或工作),到下午三点时,触发了,三点几嘞,饮茶先啊这个事件。那么我们主线程就模拟看视频或工作,自定义的线程来进行判断是否到了三点钟,强制隐藏主线程界面,当小时数不等于三点钟时显示主界面。
#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
#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); } }
#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; }
#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(); }
点击 OK 后主窗口将会隐藏,直到当前整点大于 15 点窗口将会继续显示。
项目开发过程中,某个进程可能有多个线程,当多个线程需要同时对一份内存数据进行操作时,为了保证线程安全,这时需要对进程进行加锁操作。加锁操作可以理解为牺牲时间来保证整个程序的稳定性能。
Qt 提供了 QMutex 类来进行线程的加锁操作,官方文档地址:QMutex 。我们主要使用 lock 和 unlock 来完成线程的加解锁操作。
(1)void lock() 加锁
(2)void unlock() 解锁
下面以 GUI 展示线程的加锁和解锁操作。
由结果可以看出,不加锁时计算结果出现了错误。线程加锁后计算结果符合最终的结果。
在 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实例,解锁 //可替换的内部函数 结束 }
使用 QMutex 或 QMutexLocker 类完成。参考线程锁简介的内容。
信号量 QSemaphore 是互斥锁的泛化。互斥锁只能锁定一次,信号量则可以多次获取。信号量通常用于保护一定数量的相同资源。(一般用于典型的生产者线程和消费者线程)
信号量支持两种基本操作,acquire () 和 release ():
QWaitCondition 允许一个线程告诉其他线程某种条件已经满足。一个或多个线程可以阻塞等待 QWaitCondition 使用 wakeOne () 或 wakeAll ()设置条件。使用 wakeOne () 唤醒随机选择的一个线程或使用 wakeAll () 唤醒所有线程。
QSharedMemory 提供多个线程和进程对共享内存段的访问。它还为单个线程或进程提供了一种方法来锁定内存以进行独占访问。
使用此类时,请注意以下平台差异:
在对共享内存进行读写之前记得用 lock () 锁住共享内存,完成后记得用 unlock () 释放锁,与 QMutex 类似。
当 QSharedMemory 的最后一个实例与段分离时,QSharedMemory 会自动销毁共享内存段,并且不会保留对该段的引用。
所谓的原子操作,取的就是“原子是最小的、不可分割的最小个体”的意义,它表示在多个线程访问同一个共享资源的时候,能够确保所有其他的线程都不在同一时间内访问相同的资源。也就是他确保了在同一时刻只有唯一的线程对这个资源进行访问。这有点类似互斥对象对共享资源的访问的保护,但是原子操作更加接近底层,因而比使用互斥对象效率更高。