多线程编程在现代软件开发中变得越来越重要,它能够提高应用程序的响应速度和处理性能。在Qt框架中,QThreadPool作为线程池管理工具,被频繁的使用。
目录
概述
接口介绍
底层原理解析
使用方法
概述
QThreadPool是Qt提供的一个线程池实现,用于管理和复用线程。线程池通过复用现有的线程来避免频繁创建和销毁线程带来的性能开销,适用于需要频繁执行并发任务的场景。QThreadPool内部维护了一个线程队列,可以在程序运行时动态分配和管理线程资源。
接口介绍
int activeThreadCount() const
返回当前活动(正在运行任务)线程的数量。这个数字不包括闲置等待工作的线程。
void clear()
清除线程池的任务队列。已经开始的任务将会继续完成,但队列中等待开始的任务会被取消。
bool contains(const QThread *thread) const
检查指定的线程是否属于该线程池。
int expiryTimeout() const
和void setExpiryTimeout(int expiryTimeout)
返回/设置线程过期时间,单位是毫秒。
线程在完成任务后,如果在过期时间内没有心的任务分配给它,那么它将被销毁。
默认expiryTimeout
值为30000毫秒(30秒)。
注意:
设置expiryTimeout
对已运行的线程没有影响。只有新创建的线程才会使用新的expiryTimeout
。我们建议在创建线程池之后,调用start()
之前设置expiryTimeout
。
int maxThreadCount() const
和void setMaxThreadCount(int maxThreadCount)
返回/设置线程池中允许的最大线程数。
注意:
maxThreadCount
不能为零和负数,线程池至少有一个线程。
void releaseThread()
和void reserveThread()
reserveThread()
方法的字面意思是保留线程(或者是预留线程),这里的保留线程不是指保留线程池中的线程不被销毁。该方法就是增加activeThreadCount()
返回值(活动线程的数量)。reserveThread()
提供了一种机制,使得开发者可以更精细地控制线程的并行度和资源占用。通过通知线程池外部线程的存在,它帮助线程池避免在CPU资源已经被占用时过渡创建或激活线程,从而优化了资源的使用和任务的执行效率。
假设最理想的线程数量是10,线程池的
maxThreadCount
设置为10,但在线程池外还有1个GUI线程、4个独立任务线程,当线程池满负荷工作时,程序的线程数量将达到15个线程,超出了合理的线程数,导致一系列的性能问题。所以,为了更精细地控制线程数量,当线程池外的线程都工作时,调用线程池的reserveThread()
来占用线程,使线程池只能有5个线程同时工作。
如果所有线程都在工作,调用该方法后会导致activeThreadCount()
暂时大于maxThreadCount()
。releaseThread()
方法是释放之前通过reserveThread()
方法预留的线程。
uint stackSize() const
和void setStackSize(uint stackSize)
获取/设置线程池工作线程的堆栈大小。
默认值为0,即工作线程的堆栈大小使用操作系统默认的堆栈大小。
注意:
设置堆栈大小时只对后续新创建的线程有用,对已创建或正在运行的线程没有影响。
void start(QRunnable *runnable, int priority = 0)
从线程池中拿出一个空闲的线程来运行runnable
,如果当前线程池没有空闲的线程,那么runnable
将被添加到运行队列中。
示例:
class MyTask : public QRunnable {
public:
void run() override {
qDebug() << "QRunnable Task is running in thread" << QThread::currentThread();
// 模拟任务处理
QThread::sleep(2);
qDebug() << "QRunnable Task completed in thread" << QThread::currentThread();
}
};
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
QThreadPool *threadPool = QThreadPool::globalInstance();
threadPool->setMaxThreadCount(3); // 设置最大线程数
for (int i = 0; i < 5; ++i) {
MyTask *task = new MyTask();
threadPool->start(task);
}
threadPool->waitForDone(); // 等待所有任务完成
return app.exec();
}
void start(std::function<void()> functionToRun, int priority = 0)
从线程池中拿出一个空闲的线程来运行functionToRun
,如果当前线程没有空闲的线程,那么functionToRun
将被添加到运行队列中。
示例:
void myFunctionTask() {
qDebug() << "std::function Task is running in thread" << QThread::currentThread();
// 模拟任务处理
QThread::sleep(2);
qDebug() << "std::function Task completed in thread" << QThread::currentThread();
}
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
QThreadPool *threadPool = QThreadPool::globalInstance();
threadPool->setMaxThreadCount(3); // 设置最大线程数
for (int i = 0; i < 5; ++i) {
threadPool->start(std::function<void()>(myFunctionTask));
}
threadPool->waitForDone(); // 等待所有任务完成
return app.exec();
}
bool tryStart(QRunnable *runnable)
和bool tryStart(std::function<void()> functionToRun)
尝试从线程池中拿出空闲的线程来运行runnable
或functionToRun
。
如果调用时,没有空闲的线程,则此函数直接返回false
;否则,直接拿出空闲线程来运行,并返回true
。
示例:
class MyTask : public QRunnable {
public:
void run() override {
qDebug() << "Task is running in thread" << QThread::currentThread();
// 模拟任务处理
QThread::sleep(2);
qDebug() << "Task completed in thread" << QThread::currentThread();
}
};
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
QThreadPool *threadPool = QThreadPool::globalInstance();
threadPool->setMaxThreadCount(2); // 设置最大线程数
for (int i = 0; i < 5; ++i) {
MyTask *task = new MyTask();
if (!threadPool->tryStart(task)) {
qDebug() << "Failed to start task" << i << "due to lack of available threads";
delete task; // 必须手动删除未能启动的任务以防止内存泄漏
}
}
threadPool->waitForDone(); // 等待所有任务完成
return app.exec();
}
bool tryTake(QRunnable *runnable)
尝试从线程池的等待队列中移除一个已经提交但尚未开始执行的runnable
任务。
返回true
表示移除成功,runnable
对象的所有权将转移给调用者(即使runnable->autoDelete() == true
)。
注意:
如果runnable->autoDelete() == true
,调用tryTake()
可以会删除错误的runnable
。
比如runnable
指针指向的任务执行完成后,被自动销毁了,但后面又在同一块内存分配了新的runnable
,然后调用tryTake()
时删除的是新的任务对象。
所以官方建议:只对runnable->autoDelete() == false
的任务对象调用此函数。
bool waitForDone(int msecs = -1)
最多等待msecs
毫秒,让所有线程退出并从线程池中删除所有线程。如果所有线程都被删除,则返回true
。
如果msecs
为默认值-1,则忽略超时,直到最后一个线程退出。
底层原理解析
线程状态
QSet<QThreadPoolThread *> allThreads;
QQueue<QThreadPoolThread *> waitingThreads;
QQueue<QThreadPoolThread *> expiredThreads;
QVector<QueuePage*> queue;
上面代码是QThreadPoolPrivate
类的部分成员。
QSet<QThreadPoolThread *> allThreads
allThreads
存储线程池中所有的线程对象,管理所有线程的生命周期、状态转换和清理工作。
QQueue<QThreadPoolThread *> waitingThreads
waitingThreads
存储当前处于等待状态的线程对象,用于管理空闲线程,当有新任务到达时,可以从waitingThreads
队列中取出一个线程来执行任务,从而避免频繁创建和销毁线程。
QQueue<QThreadPoolThread *> expiredThreads
expiredThreads
存储当前处于过期状态的线程对象,用于管理过期线程,过期线程可能会被销毁以释放资源。
QVector<QueuePage*> queue
queue
存储任务队列,按优先级管理任务,新任务会按优先级插入到适当的位置,以便高优先级的任务能够优先被执行。
小结:
所以,QThreadPool
中的线程状态有:活动状态、空闲状态、过期状态。
- 活动状态:
活动状态指的是当前正在执行任务的线程状态,当前状态的线程对象存储在allThreads
队列中。
- 空闲状态:
任务执行结束后线程进入空闲状态,在expiryTimeout
过期时间内,线程对象将一直存储在waitingThreads
队列中。直到超过过期时间后,线程将转为过期状态。
- 过期状态:
过期状态的线程会被批量释放。
线程池工作流程
添加任务流程:
线程池工作流程:
使用方法
子类化QRunnable
使用QThreadPool
需要将子类化QRunnable
作为线程任务对象。
class SimpleTask : public QRunnable {
protected:
void run() override {
qDebug() << "Simple task is running in thread" << QThread::currentThreadId();
QThread::sleep(2); // 模拟线程任务耗时
}
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
SimpleTask *task = new SimpleTask();
QThreadPool::globalInstance()->start(task);
QThreadPool::globalInstance()->waitForDone(); // 等待所有线程结束
return a.exec();
}
无参函数作为线程函数
void performTask()
{
qDebug() << "Performing task in thread" << QThread::currentThreadId();
}
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
QThreadPool::globalInstance()->start(performTask);
return a.exec();
}
带参函数作为线程函数
void performTask(int id)
{
qDebug() << "Performing task in thread" << id << QThread::currentThreadId();
}
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
QThreadPool::globalInstance()->start(std::bind(performTask, 2));
return a.exec();
}
Lambda表达式作为线程函数
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
QThreadPool::globalInstance()->start([]() {
qDebug() << "Lambda task running in thread" << QThread::currentThreadId();
});
return a.exec();
}