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

Qt 多线程与并发编程详解

目录

前言

1. QThread 的正确用法:工作者-控制器模式

1.1 为什么不推荐继承 QThread?

1.2 正确模式:moveToThread()

1.3 安全地停止线程

2. 线程同步

2.1 QMutex (互斥锁)

2.2 QReadWriteLock (读写锁)

2.3 QSemaphore (信号量)

2.4 QWaitCondition (等待条件)

3. 线程间通信:信号和槽

连接类型 (Qt::ConnectionType)

4. QtConcurrent 框架

4.1 QtConcurrent::run()

4.2 QFuture 和 QFutureWatcher

总结


前言

在现代桌面应用程序开发中,用户界面的流畅响应至关重要。任何耗时的操作,如复杂计算、文件 I/O、网络请求等,如果直接在主线程(GUI 线程)中执行,都会导致界面冻结,严重影响用户体验。因此,掌握多线程编程是每一位 Qt 开发者的必备技能。

本文档旨在深入探讨 Qt 中多线程和并发编程的核心概念和最佳实践,帮助你构建响应流畅、稳定可靠的应用程序。

1. QThread 的正确用法:工作者-控制器模式

初学者最容易犯的错误就是继承 QThread 并重写其 run() 方法,将耗时操作放在其中。虽然这种方法可行,但它常常会导致对线程和对象所有权的误解。

核心理念QThread 的主要职责是管理一个线程的生命周期和其事件循环,而不是作为执行耗时代码的容器。

推荐的、也是最能发挥 Qt 信号槽机制优势的模式,是将耗时任务封装在一个 QObject 子类(我们称之为“工作者”,Worker)中,然后将这个工作者对象“移动”到一个新的 QThread 实例所管理的线程中去执行。

1.1 为什么不推荐继承 QThread?

当你继承 QThread 并添加了自定义的槽函数时,这些槽函数以及对象本身实际上仍然属于创建该 QThread 对象的线程(通常是主线程),而不是 run() 方法执行所在的新线程。这意味着在 run() 之外调用这些槽函数,它们并不会在新线程中执行,这违背了多线程的初衷,并可能引发线程安全问题。

1.2 正确模式:moveToThread()

这种模式清晰地分离了线程管理和任务执行:

  • QThread (控制器): 负责启动、管理和销毁线程,并为线程提供一个事件循环。

  • QObject (工作者): 包含所有耗时任务的逻辑和数据,它的槽函数将在新线程中被执行。

实现步骤:

  1. 创建工作者类: 创建一个继承自 QObject 的类,将耗时操作封装成一个或多个公开的槽函数。

  2. 在主线程中设置和启动线程: 在主线程(例如 MainWindow)中,创建 QThreadWorker 的实例,并建立它们之间的关系。

#include <QCoreApplication>
#include <QThread>
#include <QObject>
#include <QDebug>// 推荐的方式:创建一个继承自 QObject 的 Worker 类
class Worker : public QObject
{Q_OBJECTpublic:Worker(QObject *parent = nullptr) : QObject(parent){qDebug() << "Worker 构造函数所在的线程:" << QThread::currentThreadId();}~Worker(){qDebug() << "Worker 析构函数所在的线程:" << QThread::currentThreadId();}public slots:// 所有的耗时操作都放在这里void doLongRunningTask(){qDebug() << ">>>>>> Worker::doLongRunningTask() 所在的线程:" << QThread::currentThreadId() << ">>>>>>";qDebug() << "进入工作函数,开始执行耗时操作...";for (int i = 1; i <= 3; ++i) {qDebug() << "正在工作..." << i;QThread::sleep(1);}qDebug() << "工作完成!";emit workFinished(); // 发出完成信号}signals:void workFinished(); // 定义一个完成信号
};// 这是我们的控制器类,它负责管理线程和 Worker
class Controller : public QObject
{Q_OBJECTQThread workerThread; // QThread 对象作为成员变量public:Controller(QObject *parent = nullptr) : QObject(parent){Worker *worker = new Worker; // 1. 创建 Workerworker->moveToThread(&workerThread); // 2. 将 Worker 移动到新线程// 当线程启动时,触发 Worker 的工作函数connect(&workerThread, &QThread::started, worker, &Worker::doLongRunningTask);// 当 Worker 完成工作后,安全地退出线程connect(worker, &Worker::workFinished, &workerThread, &QThread::quit);// 当线程结束后,删除 Worker 对象connect(&workerThread, &QThread::finished, worker, &Worker::deleteLater);// 当线程结束后,也可以把 Controller 也删掉(如果需要)// connect(&workerThread, &QThread::finished, this, &Controller::deleteLater);qDebug() << "准备启动线程...";workerThread.start(); // 3. 启动线程}~Controller(){qDebug() << "Controller 析构";// 确保线程在 Controller 析构前已经停止workerThread.quit();workerThread.wait();}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "主线程 ID:" << QThread::currentThreadId();// 创建控制器,它会自动设置并启动一切Controller controller;// 当线程结束后,Qt的事件循环会自动处理对象的删除,然后程序可以退出// 为了在控制台程序中看到完整流程,我们连接线程的 finished 信号到程序的 quit// QObject::connect(&controller.workerThread, &QThread::finished, &a, &QCoreApplication::quit);// 上面这句会报错,因为 workerThread 是 private 的,这里只是为了说明// 在GUI程序中,你不需要手动退出,事件循环会一直运行return a.exec();
}#include "CorrectExample.moc"
```
**运行上述代码,您会得到类似下面的输出:**
```
主线程 ID: 0x...
Worker 构造函数所在的线程: 0x...
准备启动线程...
>>>>>> Worker::doLongRunningTask() 所在的线程: 0x...  <-- 看这里!这是一个新的线程ID
进入工作函数,开始执行耗时操作...
正在工作... 1
正在工作... 2
正在工作... 3
工作完成!
Worker 析构函数所在的线程: 0x...  <-- 在新线程中析构,非常安全
```
**结论非常清晰**:通过 `moveToThread`,我们成功地让 `Worker` 对象的所有逻辑(构造函数除外)都在一个新的、由 `QThread` 对象管理的线程中执行了。这才是 `QThread` 设计的初衷和最强大的用法。希望这两个例子和解释能够帮助您彻底理解 `QThread` 的正确使用方式!

1.3 安全地停止线程

强制终止线程 (QThread::terminate()) 是非常危险的,它会立即结束线程,但不会执行任何清理代码(如释放内存、解锁互斥锁等),极易导致资源泄漏和死锁。

正确的停止方式

  1. 设置一个标志位: 在工作者对象中设置一个 volatile bool 类型的标志位,例如 m_abort.

  2. 在耗时任务中检查标志: 在循环或关键节点检查此标志位,如果为 true 则提前退出任务。

  3. 请求退出: 主线程通过调用一个槽函数来设置这个标志位为 true

  4. 调用 quit()exit(): 这会请求线程的事件循环停止。如果线程正在执行耗时代码而没有返回事件循环,quit() 不会立即生效。

  5. 调用 wait(): 等待线程完全执行完毕并退出。这可以确保在继续主线程逻辑之前,子线程已经完全清理干净。

#include <QCoreApplication>
#include <QThread>
#include <QObject>
#include <QDebug>
#include <atomic> // C++11原子操作库,用于线程安全的布尔标志// 工作者类,负责执行耗时任务
class Worker : public QObject
{Q_OBJECTprivate:// 1. 设置一个原子布尔类型的标志位。// std::atomic 可以保证多线程对它的读写操作是安全的,不会出现数据竞争。// 初始化为 false,表示不停止。std::atomic<bool> m_shouldStop{false};public:Worker(QObject *parent = nullptr) : QObject(parent) {}public slots:// 耗时任务的执行函数void doWork(){qDebug() << "工作者开始工作于线程:" << QThread::currentThreadId();int count = 0;// 2. 在耗时任务中(通常是循环)持续检查标志位while (!m_shouldStop){qDebug() << "工作中..." << count++;// 模拟耗时操作,例如读写文件、网络通信等QThread::msleep(500); // 休眠500毫秒}// 当循环结束后,意味着线程收到了停止请求并完成了任务qDebug() << "收到停止请求,工作循环结束。";emit workFinished(); // 发出工作完成信号}// 3. 提供一个公共的槽函数,用于从外部线程(如主线程)设置停止标志位void requestStop(){qDebug() << "主线程请求停止工作...";m_shouldStop = true;}signals:void workFinished(); // 工作完成信号
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "主线程 ID:" << QThread::currentThreadId();QThread* thread = new QThread;Worker* worker = new Worker;worker->moveToThread(thread);// 当线程启动后,自动开始执行 doWork()QObject::connect(thread, &QThread::started, worker, &Worker::doWork);// 当工作者发出 workFinished 信号时,我们请求线程的事件循环退出// 4. 调用 quit() 来停止事件循环QObject::connect(worker, &Worker::workFinished, thread, &QThread::quit);// 当线程最终结束后,释放 worker 和 thread 的内存QObject::connect(thread, &QThread::finished, worker, &Worker::deleteLater);QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);QObject::connect(thread, &QThread::finished, [&](){qDebug() << "线程已安全结束。";});// 启动线程thread->start();// 创建一个定时器,在3秒后从主线程调用 requestStop() 来请求停止QTimer::singleShot(3000, [=](){// 这段代码在主线程中执行worker->requestStop();});// a.exec() 会启动主线程的事件循环,使得信号槽和定时器可以工作return a.exec();
}#include "SafeStopExample.moc"

2. 线程同步

当多个线程需要访问和修改同一个共享数据时,如果没有适当的保护,就会发生“竞态条件”(Race Condition),导致数据损坏或程序崩溃。Qt 提供了多种同步原语来解决这个问题。

2.1 QMutex (互斥锁)

QMutex 是最基本的同步工具,用于保护一个“临界区”(Critical Section),确保在任何时刻只有一个线程可以执行这段代码。

  • lock(): 获取锁。如果锁已被其他线程持有,则当前线程会阻塞,直到锁被释放。

  • unlock(): 释放锁。

  • tryLock(): 尝试获取锁,如果失败则立即返回 false,不会阻塞。

最佳实践: 使用 QMutexLocker,它利用了 C++ 的 RAII (Resource Acquisition Is Initialization) 技术。在构造时自动上锁,在析构时(离开作用域)自动解锁,从而避免了忘记解锁导致的死锁问题。

#include <QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QDebug>
#include <QList>// --- 共享数据 ---
// 创建一个互斥锁实例,它将保护下面的共享计数器
QMutex mutex;
int sharedCounter = 0; // 这是所有线程都想访问和修改的共享资源// --- 线程任务 ---
// 这是一个自定义的线程类,它的任务是多次增加计数器
class WorkerThread : public QThread
{
public:// 线程启动后会自动执行 run() 函数void run() override{for (int i = 0; i < 5; ++i){// 这是关键部分:使用 QMutexLocker 来自动管理锁// 当代码执行到这一行时,QMutexLocker 的构造函数会自动调用 mutex.lock() 来获取锁。// 如果锁被其他线程占用,这个线程会在这里等待(阻塞)。QMutexLocker locker(&mutex);// ----- 临界区开始 -----// 在这里面的代码是受保护的,同一时间只有一个线程可以执行。sharedCounter++;qDebug() << this->objectName() << "将计数器增加到:" << sharedCounter;// ----- 临界区结束 -----// 当 locker 对象离开这个作用域(即 for 循环的一次迭代结束)时,// 它的析构函数会自动被调用,从而执行 mutex.unlock() 释放锁。// 这就是 RAII 技术,非常安全,可以避免忘记解锁。// 让线程稍微休眠一下,以便观察线程间的切换msleep(10);}}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "程序开始,初始计数器:" << sharedCounter;// 创建两个工作线程WorkerThread thread1;thread1.setObjectName("线程 A");WorkerThread thread2;thread2.setObjectName("线程 B");// 启动线程thread1.start();thread2.start();// 等待两个线程都执行完毕thread1.wait();thread2.wait();qDebug() << "所有线程执行完毕,最终计数器:" << sharedCounter;qDebug() << "期望的结果是 10 (每个线程增加5次)。如果没有锁,结果可能会小于10。";// a.exec(); // 对于控制台程序,可以不需要事件循环return 0;
}

2.2 QReadWriteLock (读写锁)

适用于“读多写少”的场景。它允许多个线程同时进行读操作,但写操作是互斥的。这比 QMutex 在读取频繁时效率更高。

  • lockForRead(): 获取读锁。

  • lockForWrite(): 获取写锁。写锁会阻塞所有其他读者和写者。

  • 最佳实践: 相应地使用 QReadLockerQWriteLocker

#include <QCoreApplication>
#include <QThread>
#include <QReadWriteLock>
#include <QReadLocker>
#include <QWriteLocker>
#include <QDebug>
#include <QList>// --- 共享数据 ---
// 创建一个读写锁实例
QReadWriteLock rwLock;
// 假设这是一个共享的配置或数据,有很多线程会读取它,偶尔有线程会修改它
QString sharedMessage = "初始消息";// --- 读线程任务 ---
class ReaderThread : public QThread
{
public:void run() override{for (int i = 0; i < 3; ++i){// 使用 QReadLocker 获取读锁。// 多个读线程可以同时获取读锁,它们不会互相阻塞。QReadLocker locker(&rwLock);// ----- 读操作临界区 -----qDebug() << this->objectName() << "正在读取数据:" << sharedMessage;// ----- 临界区结束 -----// locker 离开作用域时自动释放读锁。msleep(15); // 休眠以观察效果}}
};// --- 写线程任务 ---
class WriterThread : public QThread
{
public:void run() override{for (int i = 0; i < 2; ++i){// 使用 QWriteLocker 获取写锁。// 当一个线程想要获取写锁时,它必须等待所有的读锁和写锁都被释放。// 一旦写锁被获取,其他任何线程(无论是读还是写)都必须等待。QWriteLocker locker(&rwLock);// ----- 写操作临界区 -----sharedMessage = QString("%1 写入新消息").arg(this->objectName());qDebug() << this->objectName() << "成功写入数据!";// ----- 临界区结束 -----// locker 离开作用域时自动释放写锁。msleep(25); // 休眠以观察效果}}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "--- 读写锁示例 ---";const int readerCount = 3;QList<ReaderThread*> readers;for(int i = 0; i < readerCount; ++i) {readers.append(new ReaderThread());readers.last()->setObjectName(QString("读线程 %1").arg(i + 1));}WriterThread writer1;writer1.setObjectName("写线程 A");// 启动所有线程for(ReaderThread* reader : readers) {reader->start();}writer1.start();// 等待所有线程执行完毕for(ReaderThread* reader : readers) {reader->wait();delete reader; // 释放内存}writer1.wait();qDebug() << "所有线程执行完毕。";qDebug() << "最终消息内容:" << sharedMessage;return 0;
}

2.3 QSemaphore (信号量)

信号量用于保护一定数量的相同资源。它可以看作是一个“广义的互斥锁”。一个初始化为 1 的信号量等价于一个互斥锁。

  • acquire(n): 获取 n 个资源。如果资源不足,线程会阻塞。

  • release(n): 释放 n 个资源,唤醒可能正在等待的线程。

一个经典的应用场景是控制生产者-消费者问题中的缓冲区大小。

#include <QCoreApplication>
#include <QThread>
#include <QSemaphore>
#include <QMutex>
#include <QQueue>
#include <QDebug>// --- 缓冲区大小 ---
const int BufferSize = 5; // 我们的缓冲区最多只能存放5个整数// --- 同步工具 ---
// 1. 信号量 freeSlots: 记录缓冲区中“空闲”位置的数量。
//    生产者在生产前需要获取一个空闲位置。初始时,所有位置都是空闲的。
QSemaphore freeSlots(BufferSize);// 2. 信号量 usedSlots: 记录缓冲区中“已使用”位置的数量。
//    消费者在消费前需要确保有已使用的位置。初始时,没有位置被使用。
QSemaphore usedSlots(0);// 3. 互斥锁: 保护对缓冲区队列本身的读写操作。
//    因为 QQueue 本身不是线程安全的,当多个生产者或消费者同时操作队列时需要保护。
QMutex mutex;// --- 共享资源 ---
// 共享的缓冲区,我们用一个队列来模拟
QQueue<int> buffer;// --- 生产者线程 ---
class Producer : public QThread
{
public:void run() override{for (int i = 0; i < 10; ++i){// 1. 请求一个空闲位置。//    如果 freeSlots 计数器 > 0,它会减1并立即返回。//    如果 freeSlots 计数器 == 0,此线程会阻塞,直到有消费者释放了一个位置。freeSlots.acquire();// 2. 锁住缓冲区,进行写入操作mutex.lock();int value = i * 10;buffer.enqueue(value);qDebug() << "生产者" << this->objectName() << "生产了数据:" << value << ", 当前缓冲区大小:" << buffer.size();mutex.unlock();// 3. 释放一个“已使用”位置的信号。//    这会使 usedSlots 计数器加1,并可能唤醒一个正在等待的消费者线程。usedSlots.release();msleep(50); // 生产慢一点}}
};// --- 消费者线程 ---
class Consumer : public QThread
{
public:void run() override{for (int i = 0; i < 10; ++i){// 1. 请求一个“已使用”的位置。//    如果 usedSlots 计数器 > 0,它会减1并立即返回。//    如果 usedSlots 计数器 == 0 (缓冲区是空的),此线程会阻塞,直到有生产者生产了数据。usedSlots.acquire();// 2. 锁住缓冲区,进行读取操作mutex.lock();int value = buffer.dequeue();qDebug() << "消费者" << this->objectName() << "消费了数据:" << value << ", 当前缓冲区大小:" << buffer.size();mutex.unlock();// 3. 释放一个“空闲”位置的信号。//    这会使 freeSlots 计数器加1,并可能唤醒一个正在等待的生产者线程。freeSlots.release();msleep(100); // 消费慢一点}}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "--- 生产者-消费者问题示例 ---";qDebug() << "缓冲区大小:" << BufferSize;// 创建并启动线程Producer producer1;producer1.setObjectName("P1");Consumer consumer1;consumer1.setObjectName("C1");producer1.start();consumer1.start();// 等待线程结束producer1.wait();consumer1.wait();qDebug() << "所有任务完成。";return 0;
}

2.4 QWaitCondition (等待条件)

用于让线程在满足特定条件之前进入休眠等待状态,避免了使用循环不断轮询检查,从而节省 CPU 资源。它总是和 QMutex 配合使用。

  • wait(QMutex *mutex): 原子地解锁互斥锁并使线程进入等待状态。当被唤醒时,它会重新锁上互斥锁再返回。

  • wakeOne(): 随机唤醒一个正在等待的线程。

  • wakeAll(): 唤醒所有正在等待的线程。

为什么要和 QMutex 配合? 为了防止在检查条件和进入等待状态之间发生条件变化(“丢失的唤醒”问题),wait() 操作必须是原子的。

#include <QCoreApplication>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>
#include <QQueue>
#include <QDebug>// --- 缓冲区大小 ---
const int BufferSize = 5;// --- 同步工具 ---
QMutex mutex; // 必须与 QWaitCondition 配合使用的互斥锁
QWaitCondition bufferNotEmpty; // 条件:缓冲区不为空 (用于通知消费者)
QWaitCondition bufferNotFull;  // 条件:缓冲区未满 (用于通知生产者)// --- 共享资源 ---
QQueue<int> buffer; // 共享缓冲区// --- 生产者线程 ---
class Producer : public QThread
{
public:void run() override{for (int i = 0; i < 10; ++i){mutex.lock(); // 进入临界区前加锁// 1. 检查条件:如果缓冲区已满while (buffer.size() == BufferSize) {qDebug() << "生产者" << this->objectName() << "发现缓冲区已满,进入等待...";// 原子操作:// a. 解锁 mutex// b. 线程进入休眠等待状态// c. 当被唤醒时,它会重新锁上 mutex 再继续执行bufferNotFull.wait(&mutex);}// 2. 执行操作int value = i * 10;buffer.enqueue(value);qDebug() << "生产者" << this->objectName() << "生产了数据:" << value << ", 当前缓冲区大小:" << buffer.size();// 3. 通知其他线程// 唤醒一个可能正在等待“缓冲区不为空”条件的消费者线程bufferNotEmpty.wakeOne();mutex.unlock(); // 离开临界区后解锁msleep(50);}}
};// --- 消费者线程 ---
class Consumer : public QThread
{
public:void run() override{for (int i = 0; i < 10; ++i){mutex.lock(); // 进入临界区前加锁// 1. 检查条件:如果缓冲区为空while (buffer.isEmpty()) {qDebug() << "消费者" << this->objectName() << "发现缓冲区为空,进入等待...";// 原子操作:等待“缓冲区不为空”的信号bufferNotEmpty.wait(&mutex);}// 2. 执行操作int value = buffer.dequeue();qDebug() << "消费者" << this->objectName() << "消费了数据:" << value << ", 当前缓冲区大小:" << buffer.size();// 3. 通知其他线程// 唤醒一个可能正在等待“缓冲区未满”条件的生产者线程bufferNotFull.wakeOne();mutex.unlock(); // 离开临界区后解锁msleep(100);}}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "--- QWaitCondition 生产者-消费者示例 ---";Producer producer1;producer1.setObjectName("P1");Consumer consumer1;consumer1.setObjectName("C1");producer1.start();consumer1.start();producer1.wait();consumer1.wait();qDebug() << "所有任务完成。";return 0;
}

3. 线程间通信:信号和槽

Qt 的信号和槽机制是线程间通信的首选方式,因为它类型安全、简单易用,并且 Qt 内部已经处理好了所有复杂的同步细节。

连接类型 (Qt::ConnectionType)

  • Qt::AutoConnection (默认):

    • 如果信号发射者和接收者在同一线程,则行为同 Qt::DirectConnection

    • 如果信号发射者和接收者在不同线程,则行为同 Qt::QueuedConnection

  • Qt::DirectConnection: 槽函数在信号发射的线程中被立即执行。跨线程使用时必须确保槽函数是线程安全的,否则极易引发问题。

  • Qt::QueuedConnection (队列连接): 这是跨线程通信的核心。当信号发射时,Qt 会将这个事件(包含调用的槽函数和参数)放入接收者所在线程的事件队列中。当接收者线程的事件循环处理到这个事件时,才会执行对应的槽函数。这确保了槽函数总是在其所属的对象所在的线程中安全地执行。

  • Qt::BlockingQueuedConnection: 与队列连接类似,但信号发射的线程会阻塞,直到槽函数执行完毕。这可以用于需要从另一个线程同步获取结果的场景,但要小心使用,因为它可能导致死锁。

moveToThread 模式中,我们正是利用了 Qt::AutoConnection 自动变为 Qt::QueuedConnection 的特性,来安全地从工作线程向主 GUI 线程发送数据,或者从主线程向工作线程发送指令。

4. QtConcurrent 框架

QtConcurrent 是一个高级 API,它构建在 QThreadQThreadPool 之上,旨在简化常见的并发编程模式,让你无需手动管理 QThread 对象。

4.1 QtConcurrent::run()

这是最常用的函数,它可以方便地在一个后台线程中执行一个函数。它会从 Qt 的全局线程池中取一个空闲线程来执行任务。

// 假设有一个全局函数或类的静态方法
int computeSomething(int param) {QThread::sleep(2); // 模拟耗时计算return param * param;
}void MyClass::startComputation() {// 异步执行 computeSomethingQFuture<int> future = QtConcurrent::run(computeSomething, 42);// ... 此时主线程可以继续做其他事情,不会被阻塞 ...
}

4.2 QFutureQFutureWatcher

QtConcurrent::run() 会立即返回一个 QFuture 对象。QFuture 是一个占位符,代表了未来某个时刻才会知道的计算结果。

  • 阻塞式获取结果: 你可以调用 future.result() 来获取结果,但这会阻塞当前线程,直到计算完成。这在主线程中是不可取的。

  • 非阻塞式获取结果 (推荐): 使用 QFutureWatcher 来监视 QFuture 的状态。QFutureWatcher 通过信号槽机制通知你任务的进展。

示例:

#include <QtConcurrent>
#include <QFuture>
#include <QFutureWatcher>
#include <QLabel>class MyWindow : public QWidget {Q_OBJECT
public:MyWindow() {// ...resultLabel = new QLabel("正在计算...", this);watcher = new QFutureWatcher<int>(this);// 当计算完成时,调用 onFinished 槽connect(watcher, &QFutureWatcher<int>::finished, this, &MyWindow::onFinished);// 启动计算QFuture<int> future = QtConcurrent::run(this, &MyWindow::longComputation);watcher->setFuture(future);}private:int longComputation() {qDebug() << "后台计算开始于线程:" << QThread::currentThread();QThread::sleep(3);return 123;}private slots:void onFinished() {qDebug() << "结果已返回,处理于线程:" << QThread::currentThread();int result = watcher->result();resultLabel->setText(QString("计算结果: %1").arg(result));}private:QLabel *resultLabel;QFutureWatcher<int> *watcher;
};

QtConcurrent 还提供了 map, mapped, filter 等函数,用于对一个容器(如 QList)中的所有元素并行地执行某个操作,极大地简化了数据并行处理的编码。

总结

  • 首选 moveToThread: 对于需要长期运行、有复杂状态或需要与主线程频繁交互的任务,使用 moveToThread 模式是最健壮和灵活的选择。

  • 善用 QtConcurrent: 对于一次性的、简单的后台任务,QtConcurrent::run 结合 QFutureWatcher 是最快捷、最简单的解决方案。

  • 通信靠信号槽: 始终优先使用信号和槽(特别是队列连接)进行线程间通信。

  • 同步要谨慎: 只有在多个线程确实需要直接访问共享内存时,才使用 QMutex 等同步原语,并优先选择 RAII 风格的 QMutexLocker 等辅助类。

  • 严禁 terminate(): 永远不要使用 QThread::terminate() 来终止线程。

掌握了这些知识点,你就能在 Qt 中编写出高效、稳定且用户体验优秀的多线程应用程序。

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

相关文章:

  • 第五个实验——动态nat地址转换操作
  • 排查 TCP 连接中 TIME_WAIT 状态异常
  • 《C++ 实际应用系列》第二部分:内存管理与性能优化实战
  • 登建设厅锁子的是哪个网站祥云平台网站管理系统
  • 浙江省建设厅网站在哪里手机网站制作招聘
  • nat server 概念及题目
  • 试看30秒做受小视频网站深圳外贸网站制作
  • 网站营销推广怎么做网络营销推广网站建设关于公司怎么写
  • 【AI】专访 Braintrust CEO Ankur Goyal:为什么 AI 评测是产品 prototype 走向生产的唯一桥梁?
  • 大模型文生图和语音转换的调用以及向量和向量数据库RedisStack.
  • 做代练去什么网站安全合肥网站seo整站优化
  • 网站案例展示怎么做桂电做网站的毕设容易过嘛
  • QT-常用控件(一)
  • 网站开发选asp还是hph网站域名解析步骤
  • AI行业应用深度解析:从理论到实践的跨越
  • DeepMind 和罗光记团队 推出“帧链”概念:视频模型或将实现全面视觉理解
  • 外贸圈阿里巴巴微信seo什么意思
  • 【专业词汇】元认知
  • 有什么网站开发软件网页棋牌开发
  • Flutter与Dart结合AI开发实战
  • Easyx使用(数据可视化)
  • 基于单片机的大货车防偷油系统设计
  • JavaScript:神奇的ES6之旅
  • 延吉网站开发公司特别好的企业网站程序
  • Avalonia:现代跨平台UI开发的卓越之选
  • gta5网站正在建设阿里云网站建设与发布题库
  • 网页制作的网站淘宝页面设计的网站
  • 【STM32项目开源】STM32单片机人体健康监测系统
  • 车载诊断架构 --- 车载ECU故障类型详解(上)
  • 房产中介做网站wordpress 下载插件xydown