Qt中使用多线程的范式
使用线程对象QThread
以下的代码仅列出紧要的部分。
QThread是一个线程管理类
//mthread.h
class MThread : public QThread
{Q_OBJECT
public:MThread();// QThread interfacevoid printA(){qDebug()<<"A";}// QThread interface
protected:void run() override;
};void MThread::run()
{qDebug()<<"Thread:"<<QThread::currentThreadId();
}//main.cppint 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类来完成耗时的任务
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();//主线程IDemit w.prepared();return a.exec();
}
任务结束时,通过一个指定的信号来告诉主线程
//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();//主线程IDemit w.prepared();return a.exec();
}
跨线程的信号槽,默认使用了队列连接,所以任务printW是在进入了事件循环才开始执行的
//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();//主线程IDreturn a.exec();
}
Worker对象被移动到新的线程中,但是其信号可以安全地连接到主线程中对象的槽函数。Qt会自动使用队列连接,确保信号传递线程安全的。
Qt支持多种信号槽连接类型,在线程编程中尤为重要:
直接连接(DirectConnection):槽函数在信号发射的线程中立即调用。
队列连接(QueuedConnection):槽函数在接收对象所属线程的事件循环中调用。
阻塞队列连接(BlockingQueuedConnection):类似队列连接,但发送线程会阻塞直到槽函数返回。
自动连接(AutoConnection):默认方式。如果发送者和接收者在同一线程,使用直接连接;否则使用队列连接。
使用线程池QThreadPool
它是 Qt 中一个极其有用的工具,用于管理线程资源,避免频繁创建和销毁线程的开销,非常适合处理大量可并发的短期任务。
核心概念:QRunnable 和 QThreadPool
线程池的工作流程基于两个核心类:
QRunnable
:这是一个任务或工作单元的抽象基类。它类似于 Java 中的
Runnable
接口。你需要继承这个类并重写其
run()
方法。你的所有耗时代码都写在这个run()
方法里。QRunnable
本身不是QObject
的子类,因此没有内建的信号槽机制。
QThreadPool
:这是线程池管理器。它是一个全局的单例(也可以通过
new
创建私有实例)。它的主要职责是接受
QRunnable
任务,并将其分配给池中的空闲线程去执行。你可以通过
globalInstance()
获取全局线程池实例。
//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 自动管理线程池和任务调度。
安全:提供了与主线程交互的友好方式(通过
QFuture
和QFutureWatcher
)。功能强大:支持多种并行模式。
核心组件:QFuture 和 QFutureWatcher
在深入 QtConcurrent
函数之前,必须先理解这两个类:
QFuture<T>
:表示一个异步计算的结果。你可以把它想象成一个“未来的值”。
它提供了方法来查询计算状态(
isStarted()
,isFinished()
,isCanceled()
)、等待结果(waitForFinished()
)以及获取结果(result()
,results()
)。它是由
QtConcurrent
函数返回的。
QFutureWatcher<T>
:用于监视一个
QFuture
对象。它不是必须的,但如果你想在 GUI 程序中获得进度通知或完成信号,就必须使用它。它提供了信号(
started()
,finished()
,progressValueChanged()
,progressRangeChanged()
)来通知你计算的状态变化。让你能够在不阻塞主线程的情况下与异步计算交互。
//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来监控结果,并通过信号槽的方式告知结果
//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();
}