QThread的使用一点经验

本文介绍了Qt中使用QThread进行多线程编程的两种方法:继承QThread并重写run函数,以及使用QObject::moveToThread函数。文章还详细解释了如何创建、执行和终止线程,并探讨了不同连接模式在多线程通信中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文结合实际使用,初略的整理了一下Qt中使用Qthread进行多线程的编程方法。QThread实现方法有两种:

  • 继承与QThread并重写run函数;
  • 使用QObject::moveToThread函数;

线程的创建及执行

继承QThread使用方法

创建一个继承于QThread的自定义类,实现其run()函数,在run函数中的都是在新线程中调用,自定义类中的其他函数都是在主线程中执行。在主线程中直接调用start函数,启动子线程执行。例如

class WorkerThread : public QThread
{
    Q_OBJECT
    void run() override {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        qDebug()<<"Current Thread Id: "<<QThread::currentThreadId();
        emit resultReady(result);
    }
signals:
    void resultReady(const QString &s);
};

void MyObject::startWorkInAThread()
{
    WorkerThread *workerThread = new WorkerThread(this);
    connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
    connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
    workerThread->start(); //开启线程
}
QObject::moveToThread

创建一个继承于QObject的自定义类,然后通过QObject::moveToThread(QThread *)函数,把这个类移动到新的线程中,然后通过信号槽Qt::QueuedConnection或者Qt::BlockingQueuedConnection模式进行线程间通讯,信号触发的槽在新的线程中执行,新线程中的信号链接的主线程中槽则在主线程中执行。但是主线程中通过直接调用自定义类中函数或者使用Qt::DirectConnection链接的槽函数,还是在主线程中执行。链接模式的具体说明见附。
举个简单的例子:

class Worker : public QObject
{
    Q_OBJECT

public slots:
    void doWork(const QString &parameter) {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        qDebug()<<"Current Thread Id: "<<QThread::currentThreadId();
        emit resultReady(result);
    }

signals:
    void resultReady(const QString &result);
};

class Controller : public QObject
{
    Q_OBJECT
    QThread workerThread;
public:
    Controller() {
        Worker *worker = new Worker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);//在新建线程中执行槽函数doWork()
        connect(worker, &Worker::resultReady, this, &Controller::handleResults); //在主线程中执行槽函数handleResults

//      connect(this, &Controller::operate, worker, &Worker::doWork, , Qt::DirectConnection);//采用Qt::DirectConnection模式链接则槽函数doWork()在主线程中执行;
//      worker->doWork();//直接调用,则函数doWork()在主线程中执行
        workerThread.start();
    }
    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }
public slots:
    void handleResults(const QString &);
signals:
    void operate(const QString &);
};

线程的终止

继承QThread的子线程终止方法

run()函数执行完成,线程自动退出。
- 方法一:通过设置一个bool变量控制run()函数中的while循环,注意对bool变量的互锁;
- 方法二:通过isInterruptionRequested()和requestInterruption()控制run()函数中的while循环,内部以实现互锁;

void run() override
{
    while(isThreadActive) //或者while(isInterruptionRequested())
    {
        //do something...
    }
}

//在run()函数外设置isThreadActive = false或者调用requestInterruption()函数来退出while循环,结束run()函数的运行,从而退出子线程
QObject::moveToThread

通过moveToThread()创建的子线程如要结束,则必须调用quit()函数来退出,但是调用quit()函数并不会立即结束子线程,而是在等待子线程中槽函数运行完成之后,子线程才会退出。槽函数中while循环的控制可类似于上面run()函数的控制。
注意:链接到子线程中执行的槽函数会根据触发及链接的先后顺序,依次执行,如

connect(this, &Controller::operate, worker, &Worker::doWork);
connect(this, &Controller::operate, worker, &Worker::doWork2);
//则子线程会在执行完成doWork槽函数之后再去执行doWord2槽函数.

附:connect()的六中链接模式

  1. Qt::AutoConnection
  2. Qt::DirectConnection
  3. Qt::QueuedConnection
  4. Qt::BlockingQueuedConnection
  5. Qt::UniqueConnection
  6. Qt::AutoCompatConnection

前两种比较相似,都是同一线程之间连接的方式,不同的是Qt::AutoConnection是系统默认的连接方式。这种方式连接的时候,槽不是马上被执行的,而是进入一个消息队列,待到何时执行就不是我们可以知道的了,当信号和槽不是同个线程,会使用第三种QT::QueueConnection的链接方式。如果信号和槽是同个线程,调用第二种Qt::DirectConnection链接方式。

第二种Qt::DirectConnection是直接连接,也就是只要信号发出直接就到槽去执行,无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行,一旦使用这种连接,槽将会不在线程执行!。

第三种Qt::QueuedConnection和第四种Qt::BlockingQueuedConnection是相似的,都是可以在不同进程之间进行连接的,不同的是,这里第三种是在对象的当前线程中执行,并且是按照队列顺序执行。当当前线程停止,就会等待下一次启动线程时再按队列顺序执行 ,等待QApplication::exec()或者线程的QThread::exec()才执行相应的槽,就是说:当控制权回到接受者所依附线程的事件循环时,槽函数被调用,而且槽函数在接收者所依附线程执行,使用这种连接,槽会在线程执行。

第四种Qt::BlockingQueuedConnection是(必须信号和曹在不同线程中,否则直接产生死锁)这个是完全同步队列只有槽线程执行完才会返回,否则发送线程也会等待,相当于是不同的线程可以同步起来执行。

第五种Qt::UniqueConnection跟默认工作方式相同,只是不能重复连接相同的信号和槽;因为如果重复链接就会导致一个信号发出,对应槽函数就会执行多次。

第六种Qt::AutoCompatConnection是为了连接QT4 到QT3的信号槽机制兼容方式,工作方式跟Qt::AutoConnection一样。显然这里我们应该选择第三种方式,我们不希望子线程没结束主线程还要等,我们只是希望利用这个空闲时间去干别的事情,当子线程执行完了,只要发消息给主线程就行了,到时候主线程会去响应。

后记

为什么要使用moveToTread()呢。

moveToThread对比传统子类化Qthread更灵活,仅需要把你想要执行的代码放到槽,movetothread这个object到线程,然后拿一个信号连接到这个槽就可以让这个槽函数在线程里执行。可以说,movetothread给我们编写代码提供了新的思路,当然不是说子类化qthread不好,只是你应该知道还有这种方式去调用线程。轻量级的函数可以用movethread,多个短小精悍能返回快速的线程函数适用 ,无需创建独立线程类,例如你有20个小函数要在线程内做, 全部扔给一个QThread。而我觉得movetothread和子类化QThread的区别不大,更可能是使用习惯引导。又或者你一开始没使用线程,但是后边发觉这些代码还是放线程比较好,如果用子类化QThread的方法重新设计代码,将会有可能让你把这一段推到重来,这个时候,moveThread的好处就来了,你可以把这段代码的从属着movetothread,把代码移到槽函数,用信号触发它就行了。其它的话movetothread它的效果和子类化QThread的效果是一样的,槽就相当于你的run()函数,你往run()里塞什么代码,就可以往槽里塞什么代码,子类化QThread的线程只可以有一个入口就是run(),而movetothread就有很多触发的入口。

<think>好的,用户之前询问了QtConcurrent执行类成员函数的用法,现在他想要一个关于继承QRunnable来处理频繁调用任务的例子。首先,我需要回顾之前给出的建议,其中提到对于频繁调用的任务,推荐继承QRunnable而不是使用QtConcurrent::run。用户现在希望有一个具体的例子,所以需要详细说明如何实现这一点。 首先,我需要确定QRunnable的基本用法。QRunnable是Qt中用于创建可重入任务对象的基类,通常与QThreadPool一起使用。每个QRunnable对象代表一个可以在单独线程中运行的任务。继承QRunnable需要重写run()方法,并在其中定义任务的具体逻辑。 接下来,用户可能需要一个完整的示例,包括类的定义、实现和使用方法。考虑到用户之前的示例中使用了成员函数,可能需要展示如何将类的成员函数封装到QRunnable的子类中。同时,需要注意线程安全和对象生命周期的问题。 然后,考虑到频繁调用的任务,可能需要说明如何重用QRunnable对象或者如何有效地管理多个任务实例。此外,使用QThreadPool来控制线程数量,避免资源耗尽也是重点之一。 用户可能还关心如何传递参数到任务中,以及如何处理任务的完成信号。不过,QRunnable本身不提供信号槽机制,所以可能需要结合其他Qt类,比如QFutureWatcher或者自定义信号,但这在QRunnable中并不直接支持。因此,可能需要使用其他方法,比如在run()方法中发出信号(但需要注意QRunnable本身不是QObject,所以可能需要将其与QObject派生类结合使用),或者通过其他方式通知主线程。 此外,需要提到QRunnable的自动删除策略。默认情况下,QRunnable会在运行结束后自动删除,但如果需要重用对象,可以设置setAutoDelete(false)。 现在,需要构建一个具体的例子。例如,创建一个处理任务的WorkerRunnable类,继承QRunnable,并在其中重写run()方法。在main函数中,使用QThreadPool来启动多个任务。同时,展示如何传递参数,比如任务ID或其他数据。 可能还需要说明如何处理线程间的通信,比如通过信号槽,但由于QRunnable不是QObject,所以可能需要使用其他方式,比如在QRunnable内部持有指向QObject的指针,并通过调用其槽函数来传递结果。或者,在主线程中轮询结果,但这种方法效率不高。 最后,总结继承QRunnable相对于QtConcurrent的优势,比如更细粒度的线程控制,适用于需要大量并发任务的场景,以及如何管理资源。同时,提醒用户注意线程安全和资源管理的问题,比如使用互斥锁保护共享数据,避免内存泄漏等。 在整理这些思路后,构建一个结构清晰的示例,分步骤说明如何创建QRunnable子类,实现run方法,使用QThreadPool执行任务,并管理对象的生命周期。同时,强调最佳实践,比如合理设置线程池大小,处理异常情况,以及如何结合Qt的其他功能来完善多线程应用。</think>以下是通过继承`QRunnable`实现高频率任务的完整示例方案: --- ### **QRunnable 线程方案 vs QtConcurrent 核心区别** | 特性 | QRunnable | QtConcurrent::run | |--------------------|----------------------------|--------------------------| | 任务粒度 | **粗粒度**(适合复杂任务) | 细粒度(简单函数调用) | | 线程控制 | 手动管理线程池 | 自动使用全局线程池 | | 任务复用 | 支持(通过setAutoDelete) | 每次调用创建新任务 | | 生命周期管理 | 需手动控制 | 自动管理 | | 适用场景 | **高频/批量任务** | 临时性单次任务 | --- ### **完整代码实现** ```cpp #include <QRunnable> #include <QThreadPool> #include <QDebug> // ================= 自定义任务类 ================= class DataProcessor : public QRunnable { public: // 通过构造函数传递参数 explicit DataProcessor(int taskId, const QByteArray& data) : m_taskId(taskId), m_data(data) { // 设置自动删除(默认true) setAutoDelete(true); } // 核心任务逻辑 void run() override { QMutexLocker locker(&m_mutex); // 线程安全锁 // 模拟数据处理 QByteArray result = m_data.toUpper(); qDebug() << "Task" << m_taskId << "processed data:" << result.left(10) << "..."; } private: int m_taskId; // 任务标识符 QByteArray m_data; // 处理数据 static QMutex m_mutex;// 共享资源锁 }; QMutex DataProcessor::m_mutex; // 静态成员初始化 // ================= 使用示例 ================= int main() { const int TASK_COUNT = 100; // 模拟100个并发任务 QThreadPool pool; // 创建线程池 // 设置线程池参数(根据CPU核心数优化) pool.setMaxThreadCount(QThread::idealThreadCount() * 2); // 批量提交任务 for(int i = 0; i < TASK_COUNT; ++i) { QByteArray data = QString("task_data_%1").arg(i).toUtf8(); pool.start(new DataProcessor(i, data)); // 自动管理内存 } // 等待所有任务完成 pool.waitForDone(); qDebug() << "All tasks completed!"; return 0; } ``` --- ### **关键实现解析** 1️⃣ **任务类设计** ```cpp class DataProcessor : public QRunnable { // 必须实现run()函数 void run() override; }; ``` - 继承自`QRunnable`基类 - 必须重写`run()`方法作为任务入口 2️⃣ **参数传递方案** ```cpp // 通过构造函数传递参数 explicit DataProcessor(int id, const QByteArray& data) : m_taskId(id), m_data(data) {} ``` - 推荐使用**构造函数注入**参数 - 避免使用全局变量保证线程安全 3️⃣ **线程安全控制** ```cpp static QMutex m_mutex; QMutexLocker locker(&m_mutex); // 自动加锁/解锁 ``` - 使用`QMutex`保护共享资源 - `QMutexLocker`实现RAII锁管理 4️⃣ **线程池配置** ```cpp pool.setMaxThreadCount(QThread::idealThreadCount() * 2); ``` - 根据CPU核心数动态设置线程数 - 经验公式:`理想线程数 × 2`(I/O密集型场景) --- ### **生命周期管理策略** | 方案 | 实现方式 | 适用场景 | |---------------------|-----------------------------|---------------------| | 自动删除(默认) | setAutoDelete(true) | 一次性任务 | | 手动管理 | setAutoDelete(false) | 需要复用的任务对象 | | 智能指针 | QSharedPointer<QRunnable> | 复杂生命周期控制 | --- ### **性能优化建议** 1. **对象池技术**:预创建可复用的QRunnable对象 ```cpp QQueue<DataProcessor*> taskPool; // 任务对象池 ``` 2. **批量提交模式**:使用`QThreadPool::start()`的批量重载 3. **任务优先级**:通过`setPriority()`调整执行顺序 ```cpp highPriorityTask->setPriority(QRunnable::HighPriority); ``` --- ### **典型应用场景** - 网络服务器(处理并发请求) - 日志批量写入系统 - 图像渲染任务队列 - 数据库批量操作 通过继承QRunnable的方式,开发者可以更精细地控制线程行为,特别适合需要**批量处理**、**资源复用**或**特殊线程调度策略**的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值