目录
5、无锁原子操作 QAtomicInt,QAtomicInteger ,QAtomicPointer
一、前言
经过 Qt 多线程编程(一)入门篇的学习,我们已经简单的熟悉了多线程以及与多线程相关的理论知识,剖析了 Qt 提供的 QThread 库的组成和使用方法。在掌握理论的同时,我们更应该注意实践能力,这篇文章就带大家进一步的深入学习 Qt 多线程的开发及应用。
二、创建属于自己的线程
1、创建类文件
- Step1:创建一个 Qt 项目,模板选用 Qt Widgets Application。

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

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

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;
};
#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、线程运行测试
时间不在下午三点

时间在下午三点

点击 OK 后主窗口将会隐藏,直到当前整点大于 15 点窗口将会继续显示。
三、线程锁简介
项目开发过程中,某个进程可能有多个线程,当多个线程需要同时对一份内存数据进行操作时,为了保证线程安全,这时需要对进程进行加锁操作。加锁操作可以理解为牺牲时间来保证整个程序的稳定性能。
1、QMutex类
Qt 提供了 QMutex 类来进行线程的加锁操作,官方文档地址:QMutex 。我们主要使用 lock 和 unlock 来完成线程的加解锁操作。
(1)void lock() 加锁
(2)void unlock() 解锁
下面以 GUI 展示线程的加锁和解锁操作。(程序链接)
- 设置三个线程工作,不加锁时的运行结果

- 设置三个线程工作,加锁时的运行结果

由结果可以看出,不加锁时计算结果出现了错误。线程加锁后计算结果符合最终的结果。
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、条件变量 QWaitCondition
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、文件读写锁实现
简单描述同时写文件的线程只有一个,同时读文件的线程可以有很多个。有线程写数据的时候不允许读,有线程读数据的时候不允许写。(程序链接)
测试GUI如下:
- ⑴不加锁多端写入,多端读取

- ⑵加锁多端写入,多端读取

2、生产消费锁的实现
假设有生产者 A 和生产者 B,消费者 1~4。生产者 A 生产茶叶、灯泡,每秒生产茶叶10袋,灯泡5个;生产者 B 生产牛奶、插座,每秒生产牛奶5袋,插座10个。消费者 1 每秒消费茶叶 1 袋,灯泡 1 个; 消费者 2 每秒消费茶叶 2袋,插座 2 个;消费者 3 每秒消费灯泡 1 个,牛奶 1 袋;消费者 4 每秒消费插座 2个,茶叶 1 袋,灯泡 1 个,牛奶 1 袋。
生产上限:
- 茶叶 100 袋。
- 灯泡 50 个。
- 牛奶 30 袋。
- 插座 20 个。
达到上限后生产者通知消费者消费,库存没有后消费者停止消费。
①不加锁GUI演示

②加锁GUI演示
