当前位置: 首页 > news >正文

Qt-多线程编程:互斥量 信号量

一、线程

虽然Qt提供了多种多线程方式,但在实际项目中:

  • 80%的情况:使用moveToThreadQThread子类

  • 15%的情况:使用QtConcurrent处理并行计算

  • 5%的情况:使用QRunnable处理大量短期任务

所以这篇文章主要写的是moveToThread和QThread

moveToThread 与 继承QThread 的区别

1. 设计理念和架构差异

继承QThread(子类化方式)

// 传统方式:线程和工作逻辑耦合

moveToThread(对象移动方式)

// 现代方式:线程和工作对象分离

2. 关键区别对比

特性继承QThreadmoveToThread
设计原则违反单一职责原则符合单一职责原则
代码复用线程和工作逻辑绑定工作对象可在不同线程间移动
生命周期线程销毁时工作对象自动销毁需要手动管理对象生命周期
灵活性较低很高,可动态切换线程
Qt推荐不推荐推荐使用

3.继承QThread案例:

3.1. 直接继承QThread

cpp

class WorkThread : public QThread  // 直接继承

3.2. 必须重写run()方法

cpp

void run() override;  // 线程入口点

3.3. 线程控制简单直接

cpp

worktread[i]->start();  // 直接启动线程
worktread[i]->requestInterruption();  // 直接请求中断

3.4. 线程状态管理

cpp

worktread[i]->isRunning();  // 直接检查线程状态
worktread[i]->wait();       // 直接等待线程结束

3.5. 中断检查机制

cpp

while (!isInterruptionRequested()) {  // 传统方式的中断检查// 工作代码
}

3.6.整体代码

workthread.h、workthread.cpp二者是集成QThread和重写run()函数的代码

workthread.h

#ifndef WORKTHREAD_H
#define WORKTHREAD_H#include <QThread>class WorkThread : public QThread
{
public:WorkThread();
protected:void run() ;
};#endif // WORKTHREAD_H

workthread.cpp

#include "workthread.h"
#include<QDebug>
WorkThread::WorkThread() {}void WorkThread::run()
{qDebug() << "线程" << this << "启动,开始工作";while (!isInterruptionRequested()) { // 核心:循环检查中断信号for(int i=0;i<5;i++){qDebug()<<i;}QThread::msleep(100); // 短暂休眠,给中断信号响应机会}qDebug() << "线程" << this << "收到中断,即将退出";}

mainwindow.h、mainwindow.cpp二者是测试这个线程的代码

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include"workthread.h"
#include<QPushButton>
#include<QHBoxLayout>
#define MAXSIZE 5
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private:Ui::MainWindow *ui;QPushButton *startbtn,*stopbtn,*exitbtn;WorkThread *worktread[MAXSIZE];
public slots:void start();void stop();};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);startbtn=new QPushButton("开始");stopbtn=new QPushButton("停止");exitbtn=new QPushButton("退出");// 获取 MainWindow 的 centralWidget,在它上面设置布局QWidget *centralWidget = new QWidget(this);this->setCentralWidget(centralWidget);QHBoxLayout *layout = new QHBoxLayout(centralWidget); // 布局父对象设为 centralWidgetlayout->addWidget(startbtn);layout->addWidget(stopbtn);layout->addWidget(exitbtn);connect(startbtn,&QPushButton::clicked,this,&MainWindow::start);connect(stopbtn,&QPushButton::clicked,this,&MainWindow::stop);connect(exitbtn,&QPushButton::clicked,this,&MainWindow::close);
}MainWindow::~MainWindow()
{stop();for (int i = 0; i < MAXSIZE; ++i) {if (worktread[i]) {delete worktread[i];worktread[i] = nullptr;}}delete ui;
}
void MainWindow::start()
{for (int i=0;i<MAXSIZE;i++){worktread[i]=new WorkThread;}for(int i=0;i<MAXSIZE;i++){worktread[i]->start();}startbtn->setEnabled(false);stopbtn->setEnabled(true);
}
void MainWindow::stop()
{for (int i = 0; i < MAXSIZE;i++) {if (worktread[i] && worktread[i]->isRunning()) {worktread[i]->requestInterruption(); // 通知线程中断(Qt线程推荐用法)worktread[i]->wait(); // 等待线程安全退出}}startbtn->setEnabled(true);stopbtn->setEnabled(false);
}


运行结果:

ui

debug输出结果:

4.moveToThread

4.1. 对象创建和移动(核心特性)

cpp

// 创建工作对象 - 此时还在主线程
Worker *worker = new Worker;// 创建线程对象
QThread *workThread = new QThread;// ⭐ 核心特性:将对象移动到新线程
worker->moveToThread(workThread);// 启动线程(此时worker的线程亲和性已改变)
workThread->start();

4.2. 跨线程信号槽连接(核心特性)

cpp

// ⭐ 核心特性:自动跨线程通信
// 按钮点击信号(主线程) -> worker的doWork槽(工作线程)
connect(startBtn, &QPushButton::clicked, worker, &Worker::doWork);// worker信号(工作线程) -> 主窗口槽(主线程)  
connect(worker, &Worker::workFinished, this, &MainWindow::onWorkFinished);

4.3. 线程亲和性验证

cpp

void Worker::doWork()
{// ⭐ 核心特性:验证线程亲和性qDebug() << "Worker线程ID:" << QThread::currentThreadId();// 输出与主线程不同的ID,证明在工作线程执行
}

4.4. 生命周期管理(核心特性)

cpp

// ⭐ 核心特性:自动资源清理
connect(workThread, &QThread::finished, worker, &Worker::deleteLater);
connect(workThread, &QThread::finished, workThread, &QThread::deleteLater);

4.5案例


worker.h

#ifndef WORKER_H
#define WORKER_H#include <QObject>class Worker : public QObject
{Q_OBJECTpublic:explicit Worker(QObject *parent = nullptr);public slots:void doWork();signals:void workFinished();
};#endif // WORKER_H

worker.cpp

#include "worker.h"
#include <QDebug>
#include <QThread>Worker::Worker(QObject *parent) : QObject(parent)
{
}void Worker::doWork()
{qDebug() << "Worker线程ID:" << QThread::currentThreadId();for(int i = 0; i < 3; i++) {qDebug() << "工作中..." << i;QThread::msleep(500);}qDebug() << "工作完成";emit workFinished();
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>class QPushButton;
class QVBoxLayout;
class QThread;
class Worker;class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void onWorkFinished();private:QPushButton *startBtn;Worker *worker;QThread *workThread;
};#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "worker.h"
#include <QPushButton>
#include <QVBoxLayout>
#include <QThread>
#include <QDebug>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), worker(new Worker), workThread(new QThread)
{// 创建中央部件和布局QWidget *centralWidget = new QWidget(this);setCentralWidget(centralWidget);QVBoxLayout *layout = new QVBoxLayout(centralWidget);// 创建按钮startBtn = new QPushButton("开始工作", this);layout->addWidget(startBtn);// 关键步骤:将worker移动到新线程worker->moveToThread(workThread);// 连接信号槽connect(startBtn, &QPushButton::clicked, worker, &Worker::doWork);connect(worker, &Worker::workFinished, this, &MainWindow::onWorkFinished);connect(workThread, &QThread::finished, worker, &Worker::deleteLater);connect(workThread, &QThread::finished, workThread, &QThread::deleteLater);// 启动线程workThread->start();qDebug() << "主线程ID:" << QThread::currentThreadId();
}MainWindow::~MainWindow()
{workThread->quit();workThread->wait();
}void MainWindow::onWorkFinished()
{qDebug() << "收到工作完成信号";
}

4.6核心特性总结

  1. ⭐ 对象移动性moveToThread()改变对象线程亲和性

  2. ⭐ 自动跨线程:信号槽自动处理线程间通信

  3. ⭐ 资源管理:通过信号槽自动清理资源

  4. ⭐ 灵活复用:同一对象可在不同线程间移动

  5. ⭐ 事件驱动:通过信号槽控制执行,非直接调用

二、信号量

信号量基本概念

信号量(Semaphore)是一种用于控制多个线程对共享资源访问的同步机制。与互斥量(QMutex)不同,信号量可以管理多个资源实例。

案例代码解析

1. 信号量初始化

cpp

QSemaphore freeSpace(5);  // 5个空闲位置
QSemaphore usedSpace(0);  // 0个已使用位置
  • freeSpace:初始值为5,表示缓冲区有5个空位

  • usedSpace:初始值为0,表示缓冲区没有数据

2. 生产者线程工作流程

cpp

void Producer::run() {int data = 0;while (!isInterruptionRequested()) {freeSpace.acquire();      // 步骤1:等待有空闲空间buffer.append(data);      // 步骤2:生产数据qDebug() << "生产:" << data;usedSpace.release();      // 步骤3:通知有数据可用data++;msleep(200);}
}

生产者执行过程

  1. freeSpace.acquire() - 获取一个空闲位置

    • 如果freeSpace > 0:立即继续,同时freeSpace减1

    • 如果freeSpace = 0:阻塞等待,直到消费者释放空间

  2. 生产数据到缓冲区

  3. usedSpace.release() - 释放一个数据信号,usedSpace加1

3. 消费者线程工作流程

cpp

void Consumer::run() {while (!isInterruptionRequested()) {usedSpace.acquire();      // 步骤1:等待有数据可用if (!buffer.isEmpty()) {int value = buffer.takeFirst();  // 步骤2:消费数据qDebug() << "消费:" << value;}freeSpace.release();      // 步骤3:通知有空闲空间msleep(300);}
}

消费者执行过程

  1. usedSpace.acquire() - 获取一个数据

    • 如果usedSpace > 0:立即继续,同时usedSpace减1

    • 如果usedSpace = 0:阻塞等待,直到生产者生产数据

  2. 从缓冲区消费数据

  3. freeSpace.release() - 释放一个空间信号,freeSpace加1

信号量的工作原理

计数器机制

信号量内部维护一个计数器:

  • acquire():如果计数器>0,减1并继续;如果计数器=0,阻塞等待

  • release():计数器加1,并唤醒等待的线程

案例

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QSemaphore>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECT
public:MainWindow(QWidget *parent = nullptr);~MainWindow();
private slots:void on_startButton_clicked();void on_stopButton_clicked();
private:Ui::MainWindow *ui;};
#endif

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include <QSemaphore>
#include <QDebug>// 全局信号量和缓冲区
QSemaphore freeSpace(5);  // 5个空闲位置
QSemaphore usedSpace(0);  // 0个已使用位置
QList<int> buffer;class Producer : public QThread {void run() override {int data = 0;while (!isInterruptionRequested()) {freeSpace.acquire();      // 🔒 等待空闲空间buffer.append(data);qDebug() << "生产:" << data;usedSpace.release();      // 🔒 通知有数据可用data++;msleep(200);}}
};class Consumer : public QThread {void run() override {while (!isInterruptionRequested()) {usedSpace.acquire();      // 🔒 等待有数据可用if (!buffer.isEmpty()) {int value = buffer.takeFirst();qDebug() << "消费:" << value;}freeSpace.release();      // 🔒 通知有空闲空间msleep(300);}}
};MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::on_startButton_clicked()
{ui->startButton->setEnabled(false);ui->stopButton->setEnabled(true);ui->logTextEdit->clear();buffer.clear();Producer *producer = new Producer;Consumer *consumer = new Consumer;producer->start();consumer->start();ui->logTextEdit->append("生产消费开始");
}void MainWindow::on_stopButton_clicked()
{ui->startButton->setEnabled(true);ui->stopButton->setEnabled(false);ui->logTextEdit->append("生产消费停止");
}

结果:

这里可以看到消费者是按顺序的。

三、互斥量

QMutex的核心特性

1. 互斥访问

cpp

mutex.lock();
// 只有一个线程能执行这里的代码
mutex.unlock();

2. 保护临界区

  • 确保同一时间只有一个线程访问共享资源

  • 防止数据竞争和不一致

3. 线程安全

  • 保证对共享数据的操作是原子的

  • 避免脏读、脏写问题

4.案例

我们使用QMutex来保护一个共享的计数器,两个线程同时增加这个计数器,使用互斥量确保正确性。我们将创建两个线程,每个线程对计数器递增一定次数,最后检查计数器的值是否正确。

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QMutex>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void on_startButton_clicked();void on_stopButton_clicked();private:Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include <QMutex>
#include <QDebug>// 共享资源和互斥量
int sharedCounter = 0;
QMutex counterMutex;class WorkerThread : public QThread
{int m_id;
public:WorkerThread(int id) : m_id(id) {}protected:void run() override {for(int i = 0; i < 100; i++) {if(isInterruptionRequested()) return;// 🔒 使用互斥量保护共享数据counterMutex.lock();// 临界区开始int temp = sharedCounter;QThread::msleep(1);  // 模拟处理时间,增加竞争机会sharedCounter = temp + 1;qDebug() << "线程" << m_id << "增加计数器:" << sharedCounter;// 临界区结束counterMutex.unlock();QThread::msleep(10);}}
};MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::on_startButton_clicked()
{ui->startButton->setEnabled(false);ui->stopButton->setEnabled(true);ui->logTextEdit->clear();sharedCounter = 0;  // 重置计数器// 创建三个工作线程WorkerThread *thread1 = new WorkerThread(1);WorkerThread *thread2 = new WorkerThread(2);WorkerThread *thread3 = new WorkerThread(3);thread1->start();thread2->start();thread3->start();// 使用Qt的机制自动清理线程connect(thread1, &QThread::finished, thread1, &QThread::deleteLater);connect(thread2, &QThread::finished, thread2, &QThread::deleteLater);connect(thread3, &QThread::finished, thread3, &QThread::deleteLater);ui->logTextEdit->append("启动三个线程增加共享计数器...");
}void MainWindow::on_stopButton_clicked()
{ui->startButton->setEnabled(true);ui->stopButton->setEnabled(false);ui->logTextEdit->append(QString("最终计数器值: %1").arg(sharedCounter));ui->logTextEdit->append("理论上应该是300,如果没有竞争条件的话");
}

结果:

四、比较

特性QMutexQSemaphore
资源模型二进制锁(锁定/未锁定)计数资源(0-N个可用)
使用方式lock()/unlock()acquire()/release()
适用场景保护临界区控制资源访问数量
典型应用保护共享变量生产者-消费者、连接池

http://www.dtcms.com/a/528595.html

相关文章:

  • TERMSRV!WinStationLpcThread函数和TERMSRV!WinStationLpcHandleConnectionRequest函数分析
  • 网站体验方案wordpress更改前端引用
  • vue-day01
  • LLM驱动的自动化购车顾问及评测系统
  • 现代软件工程课程 个人博客作业2-结对编程项目总结
  • Elasticsearch8.4.1升级Elasticsearch9.1.5
  • 中国中小企业网站大学生招聘就业网
  • 深度学习(3)神经网络
  • FastAPI之 SQLAIchemy
  • [人工智能-大模型-70]:模型层技术 - 从数据中自动学习一个有用的数学函数的全过程,AI函数计算三大件:神经网络、损失函数、优化器
  • 网站开发最适合的浏览器wordpress下载页插件下载
  • EN 300-2006 欧松板(OSB)检测
  • 智能优化神经网络预测
  • 【Docker】镜像仓库
  • 2.Linux指令(三)
  • 【C++】哈希表:除留余散法和哈希桶的实现
  • 沧州网站运营自己做网站需要什么材料
  • PostgreSQL查不动?分区表+覆盖索引+物化视图的优化魔法了解下?
  • 多相CFD中的模型转换:Ansys Fluent中的从DPM到VOF和欧拉壁膜
  • 关于学校的网站模板免费下载高端网站建设磐石网络好
  • 在半导体制造中如何选择最佳的刻蚀方法?
  • 构建Django的Web镜像
  • 历史数据分析——锦江酒店
  • 做网站站怎么赚钱吗企业网站推广的收获与启示
  • 大厂硬件岗位笔试题库-卷11
  • 【操作系统】408操作系统核心考点精讲:第二章——进程的概念、组成与特征​
  • 基于脉冲神经网络的语音识别系统实现:识别“将榴弹从位置幺搬到位置两“命令
  • 破茧成蝶:全方位解析Java学习难点与征服之路
  • 江门网页建站模板aws 搭建wordpress
  • C语言:使用顺序表实现通讯录