1. 线程池的原理
在传统多线程编程中:
- 每来一个任务就创建一个新线程
- 任务结束后销毁线程
这样做简单,但如果任务量巨大、每个任务又很短,就会导致: - 频繁创建、销毁线程 ➔ 极大浪费系统资源 ➔ 程序变慢
✅ 线程池的出现,就是为了解决这个问题!
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
什么是线程池?
- 线程池事先创建好一定数量的线程(工作线程)
- 新任务来了,不创建新线程,而是把任务交给已有的线程来执行
- 任务完成后,线程不销毁,而是回到池中,等待下一个任务
- 大幅减少了线程创建销毁的开销,提高了程序性能
线程池组成:
线程池的要分为3个部分,这三部分配合工作就可以得到一个完整的线程池:
| 组成 | 说明 |
|---|---|
| 任务队列 | 存放所有等待执行的任务 |
| 工作线程 | 多个,循环取任务队列里的任务来执行 |
| 管理者线程 | 1个,动态监控线程数量,按需扩缩线程池 |
① 任务队列
- 存储需要处理的任务,由工作线程来取出并执行。
- 通过线程池提供的API函数(比如
start()),可以:- 把新任务添加到任务队列中(等待线程来处理)
- 或者从任务队列中删除还没执行的任务(比如
clear()、tryTake())
- 已处理完的任务,会被自动从任务队列里移除。
✅ 理解:
任务队列就像"待办事项清单",新的任务不断加进来,做完就从清单上划掉。
② 生产者线程(调用线程池添加任务的线程)
- 线程池的使用者(你的程序中的主线程、其他业务线程)就是生产者线程。
- 他们的任务是:
- 通过调用线程池API(比如
start()) - 把要处理的新任务丢进任务队列里。
✅ 理解:
生产者线程就是不停地往线程池扔任务的人,比如主程序发出10个图片处理任务。
- 通过调用线程池API(比如
③ 工作线程(处理任务队列的线程,N个)
- 线程池内部会维护一定数量的工作线程(比如4个,8个,或更多)。
- 工作线程不断从任务队列里取任务出来执行。
- 工作线程扮演的是消费者角色(消费任务队列里的任务)。
✅ 工作线程的行为规则:
- 如果任务队列为空,工作线程会被阻塞等待(用条件变量或信号量机制)。
- 一旦有新任务加入,生产者线程会"通知"(唤醒)一个或多个工作线程去继续干活!
✅ 理解: 工作线程就像在仓库门口排着的工人,一有新货(新任务),马上有人上来干活。
④ 管理者线程(通常1个)
- 管理者线程不直接处理任务,只负责管理和监控整个线程池状态。
- 它的主要工作:
- 周期性检测任务队列中待处理的任务数量
- 统计当前有多少线程是处于繁忙状态
- 动态调整线程池规模:
- 当任务过多 ➔ 可以增加一些新的工作线程
- 当任务很少 ➔ 可以减少空闲工作线程,节省资源
✅ 理解:
管理者线程就像一个车间班长,负责调度:活太多叫更多工人加班,活少了就让人休息。
![[Pasted image 20250428140801.png]]
2. QRunnable 类(表示线程池中的单个任务)
✅ 在Qt中使用线程池需要先创建任务,添加到线程池中的每一个任务都需要是一个QRunnable类型,因此在程序中需要创建子类继承QRunnable这个类,然后重写 run() 方法,在这个函数中编写要在线程池中执行的任务,并将这个子类对象传递给线程池,这样任务就可以被线程池中的某个工作的线程处理掉了。
QRunnable类关键API
| 方法 | 作用 |
|---|---|
void run() override | **必须重写!**任务执行的具体逻辑写这里 |
void setAutoDelete(bool) | 设置任务执行完后,是否自动销毁 |
bool autoDelete() const | 查询当前任务是否自动析构 |
自定义一个任务类示例
(如果需要使用Qt信号槽)
class MyWork : public QObject, public QRunnable
//继承QObject和QRunnable(多继承)
{
//必须继承QObject,才能使用信号槽机制
Q_OBJECT
public:
explicit MyWork(QObject *parent = nullptr)
{
setAutoDelete(true); // 任务完成后自动销毁
}
~MyWork();
void run() override; // 重写run(),任务逻辑写这里
};
✅ 继承QObject + QRunnable,可以发送信号、响应槽,功能更强!
(如果不需要信号槽)
class MyWork : public QRunnable
{
public:
explicit MyWork()
{
setAutoDelete(true);
}
~MyWork();
void run() override;
};
✅ 只继承QRunnable,轻量,专注执行任务。
3. QThreadPool 类(线程池管理器)
✅ Qt官方提供的线程池管理类。
QThreadPool类常用API
| 方法 | 说明 |
|---|---|
int maxThreadCount() | 获取最大线程数量 |
void setMaxThreadCount(int) | 设置最大线程数量 |
void start(QRunnable *task, int priority = 0) | 把任务扔进线程池 |
bool tryStart(QRunnable *task) | 尝试添加任务(如果线程池忙就失败) |
int activeThreadCount() | 当前正在执行任务的线程数 |
void clear() | 清空等待队列中未执行的任务 |
static QThreadPool* globalInstance() | 返回应用程序的全局线程池对象 |
Qt中的 QThreadPool 类管理了一组 QThreads, 里边还维护了一个任务队列。**QThreadPool管理和回收各个 QThread 对象,以帮助减少使用线程的程序中的线程创建成本。**每个Qt应用程序都有一个全局 QThreadPool 对象,可以通过调用 globalInstance() 来访问它。也可以单独创建一个 QThreadPool 对象使用。
线程池常用的API函数如下:
// 获取和设置线程中的最大线程个数
int maxThreadCount() const;
void setMaxThreadCount(int maxThreadCount);
// 给线程池添加任务, 任务是一个 QRunnable 类型的对象
// 如果线程池中没有空闲的线程了, 任务会放到任务队列中, 等待线程处理
void QThreadPool::start(QRunnable * runnable, int priority = 0);
// 如果线程池中没有空闲的线程了, 直接返回值, 任务添加失败, 任务不会添加到任务队列中
bool QThreadPool::tryStart(QRunnable * runnable);
// 线程池中被激活的线程的个数(正在工作的线程个数)
int QThreadPool::activeThreadCount() const;
// 尝试性的将某一个任务从线程池的任务队列中删除, 如果任务已经开始执行就无法删除了
bool QThreadPool::tryTake(QRunnable *runnable);
// 将线程池中的任务队列里边没有开始处理的所有任务删除, 如果已经开始处理了就无法通过该函数删除了
void QThreadPool::clear();
// 在每个Qt应用程序中都有一个全局的线程池对象, 通过这个函数直接访问这个对象
static QThreadPool * QThreadPool::globalInstance();
一般情况下,我们不需要在Qt程序中创建线程池对象,直接使用Qt为每个应用程序提供的线程池全局对象即可。得到线程池对象之后,调用start()方法就可以将一个任务添加到线程池中,这个任务就可以被线程池内部的线程池处理掉了,使用线程池比自己创建线程的这种多种多线程方式更加简单和易于维护。
4. 示例:线程池的基本使用
mywork.h
class MyWork : public QRunnable
{
Q_OBJECT
public:
explicit MyWork(); // 构造函数
~MyWork();
void run() override; // 任务处理逻辑
};
mywork.cpp
MyWork::MyWork() : QRunnable()
{
setAutoDelete(true); // 任务执行完后,自动delete对象
}
void MyWork::run()
{
// 写你的业务处理逻辑,比如下载、计算、图片处理等
}
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 设置线程池最大线程数,比如4个
QThreadPool::globalInstance()->setMaxThreadCount(4);
// 创建任务
MyWork* task = new MyWork;
// 把任务丢进线程池,等待执行
QThreadPool::globalInstance()->start(task);
}
| 步骤 | 内容 |
|---|---|
| 1 | 定义继承QRunnable的任务类,并重写run() |
| 2 | 通过QThreadPool::globalInstance()获取线程池对象 |
| 3 | 用start(task)把任务丢进去 |
| 👉 Qt线程池 = 把一个个小任务丢进池子,由线程池自动管理线程调度,提高性能,减少频繁创建销毁线程的开销。 |
✅【线程池 vs 单线程 vs 手动开线程】完整对比总结表
| 比较项 | 单线程 | 手动开线程(QThread) | 线程池(QThreadPool + QRunnable) |
|---|---|---|---|
| 线程数量 | 只有主线程 | 自己创建多个子线程 | 固定数量的工作线程 |
| 开销 | 小,简单 | 创建销毁线程开销大 | 创建一次复用,开销小 |
| 管理难度 | 非常简单 | 较复杂(要管理每个线程对象) | 统一由线程池自动管理 |
| 适合场景 | 简单程序、界面类应用 | 任务较重、需要独立控制线程生命周期 | 大量小任务、频繁并发场景 |
| 资源占用 | 最低 | 高(线程太多浪费内存) | 低(线程数受控) |
| 通信方式 | 直接调用、信号槽 | 信号槽、事件 | 信号槽、内部调度 |
| 控制精度 | 高(自己控制流程) | 非常高(每个线程都能独立控制) | 低(线程池自动调度) |
| 特点总结 | 简单,但容易卡顿 | 灵活强大,但易复杂 | 高效、节省资源,适合批量任务处理 |
| 典型使用 | 普通小应用、界面程序 | 需要单独处理、需要长时间工作的线程(如下载、后台同步) | 批量数据处理、任务分发、IO密集型 |
✨ 总结一句话记忆法:
- 单线程:简单,适合小项目
- 手动开线程(QThread):灵活,适合单独控制的重任务
- 线程池(QThreadPool+QRunnable):高效,适合大量小任务反复处理
✨ 场景示例
| 场景 | 推荐使用 |
|---|---|
| 界面程序,只有简单交互 | 单线程 |
| 一个大文件上传/下载,后台处理进度 | 手动开一个QThread |
| 一堆图片需要批量处理(几千张) | 线程池 |
| 网络服务器,需要处理成千上万个请求 | 线程池 |
上述整理自:https://subingwen.cn/qt/threadpool/
✅ 实战场景:使用线程池批量处理图片为灰度图
我们有多个图片文件,需要批量处理(转换为灰度图),为了不阻塞主线程(UI线程),我们使用 QThreadPool + QRunnable 来并行处理这些图片。
🔧 工程结构说明
- mainwindow.h / mainwindow.cpp ➜ 主窗口(负责UI交互与任务发起)
- imageworker.h / imageworker.cpp ➜ 自定义QRunnable任务类,用于处理图像
📄 imageworker.h
#ifndef IMAGEWORKER_H
#define IMAGEWORKER_H
#include <QObject>
#include <QRunnable>
#include <QString>
#include <QImage>
class ImageWorker : public QObject, public QRunnable
{
Q_OBJECT
public:
explicit ImageWorker(const QString &inputPath, const QString &outputPath);
void run() override;
signals:
void finished(const QString &path); // 处理完成的信号
private:
QString m_inputPath;
QString m_outputPath;
};
#endif // IMAGEWORKER_H
🔍 说明:
QObject是为了使用信号机制。QRunnable表示这是一个任务,可以丢进QThreadPool中执行。run()是必须重写的方法,线程池调用它来执行任务。
📄 imageworker.cpp
#include "imageworker.h"
#include <QImage>
#include <QDebug>
ImageWorker::ImageWorker(const QString &inputPath, const QString &outputPath)
: m_inputPath(inputPath), m_outputPath(outputPath)
{
setAutoDelete(true); // 任务执行完后自动销毁
}
void ImageWorker::run()
{
QImage image;
if (!image.load(m_inputPath)) {
qWarning() << "无法加载图片:" << m_inputPath;
return;
}
// 将图像转换为灰度图
QImage gray = image.convertToFormat(QImage::Format_Grayscale8);
if (!gray.save(m_outputPath)) {
qWarning() << "保存灰度图失败:" << m_outputPath;
return;
}
emit finished(m_outputPath); // 发射处理完成信号
}
🔍 说明:
convertToFormat(QImage::Format_Grayscale8):将图像转换为灰度格式emit finished(...):通知主线程处理完成,可以更新UI等
📄 mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QThreadPool>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void onStartProcessing();
void onTaskFinished(const QString &path);
private:
Ui::MainWindow *ui;
QThreadPool *m_pool;
};
#endif // MAINWINDOW_H
📄 mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "imageworker.h"
#include <QFileDialog>
#include <QDir>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_pool(new QThreadPool(this)) // 初始化线程池
{
ui->setupUi(this);
// 设置线程池最大并发数
m_pool->setMaxThreadCount(4);
connect(ui->startButton, &QPushButton::clicked, this, &MainWindow::onStartProcessing);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::onStartProcessing()
{
// 选择输入图片文件夹
QString inputDir = QFileDialog::getExistingDirectory(this, "选择图片文件夹");
if (inputDir.isEmpty()) return;
// 创建输出文件夹
QString outputDir = inputDir + "/gray";
QDir().mkpath(outputDir); // 若不存在则创建
QDir dir(inputDir);
QStringList files = dir.entryList(QStringList() << "*.png" << "*.jpg", QDir::Files);
for (const QString &file : files) {
QString inPath = inputDir + "/" + file;
QString outPath = outputDir + "/" + file;
// 创建任务对象
ImageWorker *task = new ImageWorker(inPath, outPath);
// 连接任务完成信号(跨线程安全)
connect(task, &ImageWorker::finished, this, &MainWindow::onTaskFinished, Qt::QueuedConnection);
// 启动任务
m_pool->start(task);
}
}
void MainWindow::onTaskFinished(const QString &path)
{
qDebug() << "处理完成:" << path;
// 可以在此更新进度条、UI提示等
}
💡 UI设计要求
| 控件名 | 说明 |
|---|---|
startButton | 开始处理按钮 |
✅ 关键语法讲解
| 语法 | 说明 |
|---|---|
QThreadPool::start(task) | 将任务对象指针交给线程池执行 |
QRunnable::run() | 线程池线程执行的核心代码 |
setAutoDelete(true) | 任务执行完后自动释放内存 |
Qt::QueuedConnection | 确保跨线程信号槽连接安全 |
QImage::convertToFormat | 图像格式转换 |

1万+

被折叠的 条评论
为什么被折叠?



