Qt-多线程编程:互斥量 信号量
一、线程
虽然Qt提供了多种多线程方式,但在实际项目中:
80%的情况:使用
moveToThread或QThread子类15%的情况:使用
QtConcurrent处理并行计算5%的情况:使用
QRunnable处理大量短期任务
所以这篇文章主要写的是moveToThread和QThread
moveToThread 与 继承QThread 的区别
1. 设计理念和架构差异
继承QThread(子类化方式)
// 传统方式:线程和工作逻辑耦合
moveToThread(对象移动方式)
// 现代方式:线程和工作对象分离
2. 关键区别对比
| 特性 | 继承QThread | moveToThread |
|---|---|---|
| 设计原则 | 违反单一职责原则 | 符合单一职责原则 |
| 代码复用 | 线程和工作逻辑绑定 | 工作对象可在不同线程间移动 |
| 生命周期 | 线程销毁时工作对象自动销毁 | 需要手动管理对象生命周期 |
| 灵活性 | 较低 | 很高,可动态切换线程 |
| 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核心特性总结
⭐ 对象移动性:
moveToThread()改变对象线程亲和性⭐ 自动跨线程:信号槽自动处理线程间通信
⭐ 资源管理:通过信号槽自动清理资源
⭐ 灵活复用:同一对象可在不同线程间移动
⭐ 事件驱动:通过信号槽控制执行,非直接调用
二、信号量
信号量基本概念
信号量(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);}
}生产者执行过程:
freeSpace.acquire()- 获取一个空闲位置如果
freeSpace > 0:立即继续,同时freeSpace减1如果
freeSpace = 0:阻塞等待,直到消费者释放空间
生产数据到缓冲区
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);}
}消费者执行过程:
usedSpace.acquire()- 获取一个数据如果
usedSpace > 0:立即继续,同时usedSpace减1如果
usedSpace = 0:阻塞等待,直到生产者生产数据
从缓冲区消费数据
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,如果没有竞争条件的话");
}
结果:

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