Qt中使用多线程的范式

使用线程对象QThread

以下的代码仅列出紧要的部分。

QThread是一个线程管理类
cpp 复制代码
//mthread.h
class MThread : public QThread
{
    Q_OBJECT
public:
    MThread();

    // QThread interface
    void printA()
    {
        qDebug()<<"A";
    }

    // QThread interface
protected:
    void run() override;
};

void MThread::run()
{
    qDebug()<<"Thread:"<<QThread::currentThreadId();
}




//main.cpp

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MThread* mt=new MThread;

    MainWindow w;
    QObject::connect(&w,&MainWindow::prepared,mt,&MThread::printA);
    w.show();

qDebug()<<QThread::currentThreadId();//主线程ID

//这里没有调用mt->start();线程的任务函数MThread::run()没有执行

    emit w.prepared();
    return a.exec();
}
使用一个继承自QObject的Worker类来完成耗时的任务
cpp 复制代码
void MThread::run()
{
    qDebug()<<"Thread:"<<QThread::currentThreadId();
}



//worker.cpp
Worker::Worker(QObject *parent) : QObject(parent)
{
    qDebug()<<"create worker at thread:"<<QThread::currentThreadId();
}

void Worker::printW()
{
    for(int i=0;i<10;i++)
    {
        QThread::sleep(1);
        qDebug()<<"."<<i<<"at thread:"<<QThread::currentThreadId();
    }
}




//main.cpp
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //当使用QThread时,Worker的任务是可以执行起来的,但是使用MThread时,线程任务是没有执行起来的--原因在于MThread的线程函数run()内没有开启事件循环
    MThread* mt=new MThread;
//    QThread* mt=new QThread;

    MainWindow w;
    w.show();

    //一种推荐的多线程使用范式,继承自QObject的Worker类,将其移入子线程中去,它的槽函数将在子线程中执行
    Worker* wk=new Worker();
    wk->moveToThread(mt);
    QObject::connect(&w,&MainWindow::prepared,wk,&Worker::printW);
    mt->start();
    qDebug()<<QThread::currentThreadId();//主线程ID

    emit w.prepared();
    return a.exec();
}
任务结束时,通过一个指定的信号来告诉主线程
cpp 复制代码
//mthread.cpp
MThread::MThread()
{

}
void MThread::run()
{
    qDebug()<<"Thread:"<<QThread::currentThreadId();

    this->exec();
}



//worker.cpp
Worker::Worker(QObject *parent) : QObject(parent)
{
    qDebug()<<"create worker at thread:"<<QThread::currentThreadId();
}

void Worker::printW()
{
    for(int i=0;i<10;i++)
    {
        QThread::sleep(1);
        qDebug()<<"."<<i<<"at thread:"<<QThread::currentThreadId();
    }
    emit taskDone();
}




//main.cpp
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    MThread* mt=new MThread;
//    QThread* mt=new QThread;

    MainWindow w;
    w.show();

    //一种推荐的多线程使用范式,继承自QObject的Worker类,将其移入子线程中去,它的槽函数将在子线程中执行
    Worker* wk=new Worker();
    wk->moveToThread(mt);
    QObject::connect(&w,&MainWindow::prepared,wk,&Worker::printW);
    //taskDone信号是在子线程中发出的
    QObject::connect(wk,&Worker::taskDone,&w,&MainWindow::hide);//当任务完成后,将窗体隐藏
    mt->start();
    qDebug()<<QThread::currentThreadId();//主线程ID

    emit w.prepared();
    return a.exec();
}
跨线程的信号槽,默认使用了队列连接,所以任务printW是在进入了事件循环才开始执行的
cpp 复制代码
//mthread.cpp
MThread::MThread()
{

}
void MThread::run()
{
    qDebug()<<"Thread:"<<QThread::currentThreadId();

    this->exec();
}



//worker.cpp
Worker::Worker(QObject *parent) : QObject(parent)
{
    qDebug()<<"create worker at thread:"<<QThread::currentThreadId();
}

void Worker::printW()
{
    for(int i=0;i<10;i++)
    {
        qDebug()<<"."<<i<<"at thread:"<<QThread::currentThreadId();

        QThread::sleep(1);
    }
    emit taskDone();
}




//main.cpp
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //当使用QThread时,Worker的任务是可以执行起来的,但是使用MThread时,线程任务是没有执行起来的
    MThread* mt=new MThread;
//    QThread* mt=new QThread;

    MainWindow w;
    w.show();

    //一种推荐的多线程使用范式,继承自QObject的Worker类,将其移入子线程中去,它的槽函数将在子线程中执行
    Worker* wk=new Worker();
    wk->moveToThread(mt);
    QObject::connect(&w,&MainWindow::prepared,wk,&Worker::printW);
    //taskDone信号是在子线程中发出的
    QObject::connect(wk,&Worker::taskDone,&w,&MainWindow::hide);//当任务完成后,将窗体隐藏
    mt->start();

    //注意,这里打印的顺序很能说明一个问题:跨线程的信号槽,是在队列中执行的,所以即使emit w.prepared()在主线程打印之前
    //实际也是先打印了主线程ID,然后是进了线程对象的run()中打印了子线程ID,再然后是进了子线程的事件循环,
    //再之后开始打印任务printW()
    //create worker at thread: 0x53e4
//    main thread: 0x53e4
//    Thread: 0x47d0
//    . 0 at thread: 0x47d0
//    . 1 at thread: 0x47d0
    //...
    emit w.prepared();
    qDebug()<<"main thread:"<<QThread::currentThreadId();//主线程ID

    return a.exec();
}
Worker对象被移动到新的线程中,但是其信号可以安全地连接到主线程中对象的槽函数。Qt会自动使用队列连接,确保信号传递线程安全的。

Qt支持多种信号槽连接类型,在线程编程中尤为重要:

  1. 直接连接(DirectConnection):槽函数在信号发射的线程中立即调用。

  2. 队列连接(QueuedConnection):槽函数在接收对象所属线程的事件循环中调用。

  3. 阻塞队列连接(BlockingQueuedConnection):类似队列连接,但发送线程会阻塞直到槽函数返回。

  4. 自动连接(AutoConnection):默认方式。如果发送者和接收者在同一线程,使用直接连接;否则使用队列连接。

使用线程池QThreadPool

它是 Qt 中一个极其有用的工具,用于管理线程资源,避免频繁创建和销毁线程的开销,非常适合处理大量可并发的短期任务。

核心概念:QRunnable 和 QThreadPool

线程池的工作流程基于两个核心类:

  1. QRunnable

    • 这是一个任务工作单元 的抽象基类。它类似于 Java 中的 Runnable 接口。

    • 你需要继承这个类并重写其 run() 方法。你的所有耗时代码都写在这个 run() 方法里。

    • QRunnable 本身不是 QObject 的子类,因此没有内建的信号槽机制。

  2. QThreadPool

    • 这是线程池管理器 。它是一个全局的单例(也可以通过 new 创建私有实例)。

    • 它的主要职责是接受 QRunnable 任务,并将其分配给池中的空闲线程去执行。

    • 你可以通过 globalInstance() 获取全局线程池实例。

cpp 复制代码
//task.cpp
Task::Task(int id):m_id{id}
{

}
void Task::run()
{
    qDebug() << "Task" << m_id << "started in thread:" << QThread::currentThreadId();

    // 模拟耗时操作
    QThread::sleep(1);

    qDebug() << "Task" << m_id << "finished";

    // 注意:这里不能直接操作UI部件!需要通过信号槽(如果与QObject结合)或其它线程安全的方式与主线程通信。
}



//main.cpp
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 2. 获取全局线程池实例
    QThreadPool* pool = QThreadPool::globalInstance();

    qDebug() << "Max threads:" << pool->maxThreadCount();
    // 3. 创建并提交多个任务
    for (int i = 0; i < 10; ++i)
    {
        Task* task = new Task(i);
        pool->start(task); // 线程池会取得任务的所有权(如果autoDelete为true)
        QThread::msleep(300);
    }

    qDebug() << "All tasks submitted. Active threads:" << pool->activeThreadCount();
    // 等待所有任务完成(可选)
    pool->waitForDone();
    qDebug() << "All tasks completed.";
    return a.exec();
}
使用QtConcurrent

QtConcurrent 是一个命名空间,提供了一系列用于编写多线程程序 的高级函数。它构建在 QThreadPool 之上,但抽象程度更高,使用起来更加简单和直观。

核心思想:基于函数式编程(Map、Filter、Reduce)模型,让你像调用普通函数一样实现并行操作。

主要优势:

  • 极其简单:只需一行代码就能启动并行计算。

  • 无需管理线程:完全由 Qt 自动管理线程池和任务调度。

  • 安全 :提供了与主线程交互的友好方式(通过 QFutureQFutureWatcher)。

  • 功能强大:支持多种并行模式。

核心组件:QFuture 和 QFutureWatcher

在深入 QtConcurrent 函数之前,必须先理解这两个类:

  1. QFuture<T>

    • 表示一个异步计算的结果。你可以把它想象成一个"未来的值"。

    • 它提供了方法来查询计算状态(isStarted(), isFinished(), isCanceled())、等待结果(waitForFinished())以及获取结果(result(), results())。

    • 它是由 QtConcurrent 函数返回的。

  2. QFutureWatcher<T>

    • 用于监视 一个 QFuture 对象。它不是必须的,但如果你想在 GUI 程序中获得进度通知或完成信号,就必须使用它。

    • 它提供了信号(started(), finished(), progressValueChanged(), progressRangeChanged())来通知你计算的状态变化。

    • 让你能够在不阻塞主线程的情况下与异步计算交互。

cpp 复制代码
//main.cpp
void workFunc()
{
    //一个耗时的任务
    QThread::sleep(1);
    qDebug()<<"work at thread:"<<QThread::currentThreadId();
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QFuture<void> future = QtConcurrent::run(workFunc);

    QThread::sleep(3);
    qDebug()<<"main thread:"<<QThread::currentThreadId();
    return a.exec();
}
使用watcher来监控结果,并通过信号槽的方式告知结果
cpp 复制代码
//main.cpp
int workFunc()
{
    //一个耗时的任务
    QThread::sleep(2);
    qDebug()<<"work at thread:"<<QThread::currentThreadId();
    return 0;
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QFuture<int> future = QtConcurrent::run(workFunc);

    //使用watcher来监控结果,并通过信号槽的方式告知结果
    QFutureWatcher<int> *watcher = new QFutureWatcher<int>();
    QObject::connect(watcher, &QFutureWatcher<int>::finished, &a, [watcher]() {
        int res = watcher->future().result();
        qDebug() << "res:" << res;
        watcher->deleteLater();
    });
    watcher->setFuture(future);


    QThread::sleep(1);
    qDebug()<<"main thread:"<<QThread::currentThreadId();
    return a.exec();
}
相关推荐
攻城狮7号5 小时前
【AI时代速通QT】第七节:Visual Studio+Qt 开发指南
c++·qt·跨平台·visual studio·qt vs tools
极地星光6 小时前
如何使用 Qt Creator 高效调试
qt
轩情吖6 小时前
Qt常用控件之QWidget(三)
开发语言·c++·qt·控件·cursor·qwidget·windowopacity
O_o3818 小时前
QT多窗口跳转
开发语言·qt
大橘9 小时前
【qml-10】Quick3D实现机器人渲染(mesh)记录
qt·机器人·qml
轩情吖14 小时前
Qt常用控件之QLabel(一)
开发语言·数据库·c++·qt·小程序·qlabel·桌面开发
Larry_Yanan17 小时前
QML学习笔记(四)QML新手入门其二:通过MouseArea让Rectangle实现鼠标三态
笔记·qt·ui
郝学胜-神的一滴19 小时前
QT与Spring Boot通信:实现HTTP请求的完整指南
开发语言·c++·spring boot·后端·qt·程序人生·http
m0_635647481 天前
信号与槽已经使用connect语句连接,并且参数也匹配,但是发送信号以后不执行槽函数?
开发语言·qt