【Qt】线程池与全局信号实现异步协作

Qt线程池与全局信号实现异步协作

利用Qt的线程池并发执行任务,通过全局信号跨线程传递结果,配合事件循环阻塞等待,实现了多任务并行处理与主线程同步收尾

一、使用流程
  1. 初始化基础组件
    • 创建QCoreApplication实例(Qt 程序入口,提供事件循环核心)。
    • 定义GlobalSignal单例(全局信号载体,跨线程传递结果)。
  2. 准备结果存储与同步机制
    • QVariant result1/2/3:通用类型容器,存储不同任务的结果(支持任意数据类型,如字符串、自定义对象)。
    • QMutex mutex:保护共享结果变量,避免多线程同时写入导致的数据混乱(线程安全核心)。
    • finishedCount计数器:记录已完成的任务数,用于判断是否所有任务都执行完毕。
  3. 创建事件循环等待机制
    • QEventLoop loop:局部事件循环,让主线程在任务执行期间阻塞等待(而非直接退出),直到所有任务完成后通过loop.quit()退出。
  4. 连接全局信号与结果收集逻辑
    • 主线程连接GlobalSignal::resultReady信号,通过taskType(0/1/2)区分不同任务的结果,存入对应result变量。
    • 每次接收结果后,finishedCount递增,当等于总任务数(3)时,触发loop.quit(),结束等待。
  5. 提交任务到线程池
    • 通过QThreadPool::globalInstance()获取全局线程池,设置最大线程数(3)控制并发量。
    • 用wrapRunnable将 lambda 函数包装为QRunnable任务(自动销毁autoDelete(true)),通过pool->start()提交:
      • 任务内部模拟耗时操作(QThread::msleep),生成结果。
      • 通过QMetaObject::invokeMethod在主线程发射resultReady信号(Qt::QueuedConnection确保跨线程安全),传递结果和任务类型。
  6. 等待任务完成并处理结果
    • loop.exec()启动事件循环,主线程阻塞等待所有任务完成。
    • pool->waitForDone()确保线程池内所有任务彻底结束(避免残留线程)。
    • 打印所有结果,完成流程。
二、原理与知识点凝练
  1. Qt 线程池(QThreadPool
    • 作用:管理线程生命周期,避免频繁创建 / 销毁线程的开销,自动调度线程执行QRunnable任务。
    • 关键方法:start(QRunnable*)提交任务、setMaxThreadCount(int)控制最大并发数、waitForDone()阻塞等待所有任务完成。
  2. 任务封装(QRunnable与 lambda)
    • QRunnable是线程池任务的基类,通过重写run()定义任务逻辑。
    • wrapRunnable将 lambda 函数包装为QRunnable子类,简化任务定义(无需手动创建子类)。
    • setAutoDelete(true):任务执行后自动销毁,避免内存泄漏(核心机制)。
  3. 跨线程通信(全局信号 +QMetaObject::invokeMethod
    • 全局信号单例GlobalSignal:统一信号源,避免每个任务单独定义信号,简化多任务结果传递。
    • QMetaObject::invokeMethod:确保信号在主线程发射(Qt::QueuedConnection),解决跨线程信号槽的线程安全问题(Qt 信号槽跨线程默认通过事件队列传递,自动处理线程切换)。
  4. 线程安全(QMutex
    • 多线程同时写入共享结果变量(result1/2/3)时,QMutex通过互斥锁保证同一时间只有一个线程修改数据,避免竞态条件。
  5. 阻塞等待(QEventLoop
    • 主线程需要等待异步任务完成后再处理结果,QEventLoop提供局部事件循环,通过exec()阻塞、quit()唤醒,实现 "等待但不冻结应用" 的效果(适合需要同步等待异步结果的场景)。
  6. 通用结果存储(QVariant
    • 可存储任意 Qt 支持的类型(字符串、数字、自定义对象等),无需为不同任务结果定义特定变量类型,提升代码通用性。

通过 "线程池调度任务 + 全局信号传递结果 + 事件循环等待 + 互斥锁保安全" 的组合,实现了多任务并行执行与结果收集的核心需求。核心亮点是利用 Qt 的信号槽机制和事件循环,优雅解决了跨线程通信和同步等待问题,同时通过 lambda 和QVariant简化了代码实现。

c++ 复制代码
#include <QCoreApplication>
#include <QThreadPool>
#include <QRunnable>
#include <QEventLoop>
#include <QMutex>
#include <QDebug>
#include <QVariant>

// 1. 全局信号载体(单例):用于跨线程传递结果
class GlobalSignal : public QObject {
    Q_OBJECT
public:
    static GlobalSignal* instance() {
        static GlobalSignal ins;
        return &ins;
    }
signals:
    // 信号:taskType区分任务类型,result传递结果(通用类型QVariant)
    void resultReady(int taskType, const QVariant& result);
};

// 2. 任务包装器:将lambda转换为QRunnable
auto wrapRunnable = [](std::function<void()> func) {
    class LambdaRunnable : public QRunnable {
    public:
        LambdaRunnable(std::function<void()> f) : func(f) {
            setAutoDelete(true); // 任务执行后自动销毁
        }
        void run() override { func(); } // 执行任务逻辑
    private:
        std::function<void()> func;
    };
    return new LambdaRunnable(func);
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    // 3. 准备接收结果的变量(根据任务类型定义)
	QVariant result1, result2, result3; // 用QVariant存储任意类型结果
	QMutex mutex; // 保护结果变量的线程安全
	int finishedCount = 0; // 任务完成计数器
	const int totalTasks = 3; // 总任务数

	// 4. 事件循环:等待所有任务完成
	QEventLoop loop;

	// 5. 连接全局信号,收集结果
	QObject::connect(GlobalSignal::instance(), &GlobalSignal::resultReady,
                 [&](int taskType, const QVariant& result) {
    			QMutexLocker locker(&mutex); // 加锁保护共享变量
    			// 根据任务类型存储结果
                switch (taskType) {
                case 0: result1 = result; break;
                case 1: result2 = result; break;
                case 2: result3 = result; break;
                }
                // 所有任务完成后退出事件循环
                if (++finishedCount == totalTasks) {
                    loop.quit();
                }
		});

        // 6. 获取线程池实例
        QThreadPool* pool = QThreadPool::globalInstance();
        pool->setMaxThreadCount(3); // 设置最大线程数

        // 7. 提交任务1:模拟任务(如查询1)
        pool->start(wrapRunnable([=]() {
            // 模拟耗时操作(如数据库查询)
            QThread::msleep(1000);
            QVariant result("任务1结果"); // 实际场景替换为真实结果
            // 发送结果到主线程(通过全局信号)
            QMetaObject::invokeMethod(GlobalSignal::instance(), [=]() {
                emit GlobalSignal::instance()->resultReady(0, result);
            }, Qt::QueuedConnection);
        }));

        // 8. 提交任务2:模拟任务(如查询2)
        pool->start(wrapRunnable([=]() {
            QThread::msleep(800);
            QVariant result("任务2结果");
            QMetaObject::invokeMethod(GlobalSignal::instance(), [=]() {
                emit GlobalSignal::instance()->resultReady(1, result);
            }, Qt::QueuedConnection);
        }));

        // 9. 提交任务3:模拟任务(如查询3)
        pool->start(wrapRunnable([=]() {
            QThread::msleep(1200);
            QVariant result("任务3结果");
            QMetaObject::invokeMethod(GlobalSignal::instance(), [=]() {
                emit GlobalSignal::instance()->resultReady(2, result);
            }, Qt::QueuedConnection);
        }));

        // 10. 等待所有任务完成
        loop.exec(); // 阻塞等待事件循环退出
        pool->waitForDone(); // 确保线程池所有任务结束

        // 11. 处理结果
        qDebug() << "最终结果:";
        qDebug() << "任务1:" << result1.toString();
        qDebug() << "任务2:" << result2.toString();
        qDebug() << "任务3:" << result3.toString();
        return a.exec();
}
相关推荐
IT码农-爱吃辣条2 小时前
Three.js 初级教程大全
开发语言·javascript·three.js
☺����2 小时前
实现自己的AI视频监控系统-第一章-视频拉流与解码2
开发语言·人工智能·python·音视频
染翰3 小时前
lua入门以及在Redis中的应用
开发语言·redis·lua
王者鳜錸3 小时前
PYTHON让繁琐的工作自动化-函数
开发语言·python·自动化
兔老大RabbitMQ3 小时前
git pull origin master失败
java·开发语言·git
tt5555555555554 小时前
C/C++嵌入式笔试核心考点精解
c语言·开发语言·c++
xiao助阵4 小时前
python实现梅尔频率倒谱系数(MFCC) 除了傅里叶变换和离散余弦变换
开发语言·python
科大饭桶4 小时前
C++入门自学Day14-- Stack和Queue的自实现(适配器)
c语言·开发语言·数据结构·c++·容器
★YUI★4 小时前
学习游戏制作记录(制作系统与物品掉落系统)8.16
学习·游戏·ui·unity·c#