一、为什么需要用线程池
现在所有的高性能服务器程序,几乎都会使用到线程池技术,从而更好且有效的榨干服务器性能。
1、开多少个线程可以达到性能最佳
不知道,你有没有这个疑问?这是一种常见的线程使用方式:
class MyThread: public QThread
{
public:
virtual void run() override
{
// ...
}
};
MyThread th;
th.start();
线程的创建和销毁是有性能开销的,当我们有少量业务需要处理时,我们可以放到线程中完成,甚至可以多开几个线程并行处理。
那么,问题来了,如果需要海量的数据处理,难道我们无休止的开线程下去吗?
首先,你要明白CPU的性能是有限的,每个线程好比一个处理时间片,多个线程之间切换处理,CPU线程上下文来回切换,这个也是需要消耗时间的。
所以,物极必反,当线程数量到达一个点后,可能消耗在线程切换的时间,会大于实际线程处理业务的时间,这个可以想象的到。
那么很容易明白:线程数并不是越多越好,而是某个范围或者某个经验值。
一般来讲,我们可以认为,最佳性能线程数==CPU逻辑核心数量,比如CPU是4核8线程,那么开8个线程可以达到性能最佳。
一般电脑是开启超线程的,也就是4核可以模拟出8个逻辑核,故称4核8线程。
QThreadPool线程池默认最大线程数,也是CPU逻辑Core的数量。
严格意义来讲,最佳线程数还与处理业务类型有关,如业务属于IO密集型、CPU密集型,根据经验推断:
-
IO密集型,频繁读取磁盘上的数据,或者需要通过网络远程调用接口。线程数经验值是:2N,其中N代表CPU逻辑Core数;
-
CPU密集型,非常复杂的调用,循环次数很多,或者递归调用层次很深等。线程数经验值是:N + 1,其中N代表CPU逻辑Core数。
但是对于线程数的深入讨论研究,不在本文范围内。
2、线程池的原理
通过上一节,我们知道了,最佳性能线程数可以认为等于CPU逻辑核心数量N。
所以我们设计程序,为了得到更好的性能,需要实现如下的需求:
-
限制创建最大线程数量<=N;
-
尽可能复用线程,避免频繁创建和销毁线程资源,降低无谓消耗;
-
线程在空闲时,应该休息,避免占用CPU资源;
-
线程在有业务需要处理时,需要激活;
-
当业务来了,这N个线程如何分配;
-
……
上述问题,感觉很麻烦,对吧。别担心,QThreadPool线程池就是干这些的。
线程池,属于对象池,对象池都是为了复用,以避免频繁申请和释放对象所造成的性能损失。
线程池创建好后,池内默认一个线程也没有,当通过相关函数加入任务后,线程池根据任务数量会自动创建线程,任务会合理分配到各个线程上执行,但是线程总数量不会超过设定的最大值。
若任务处理完毕,则池内所有线程进入挂起状态,不占用CPU时间片,待任务再次到来,便会激活部分或全部线程,处理任务。
若任务过多,当前没有空闲的线程,则新增任务会被放置到缓存队列中,等待线程空闲后,再进行处理,这样,每个任务与线程可以有一个合理的分配,相当于实现了业务处理的负载均衡。故而可以以最好的性能来处理业务。
这就是线程池,存在的重要意义。
在设计高性能程序时,离不开线程池的加持。
小结:
线程池严格限制线程数量,并对线程对象创建和释放进行了封装,实现了线程对象的最大复用。对任务与线程进行了合理分配。从限制线程数、线程复用、任务分配三方面减少无谓消耗,提升了软件业务处理性能。
二、QThreadPool线程池的使用
QThreadPool的方法很少,封装的足够完备,使用也是很简单的。
1、举例说明QThreadPool的使用
语言说的再多,都很晦涩,下面看一个例子main.cpp。
#include <QCoreApplication>
#include <QThreadPool>
#include <QDebug>
class Task1 : public QRunnable
{
public:
Task1()
{ }
virtual ~Task1() override
{
qDebug() << "~Task1()";
}
virtual void run() override
{
qDebug() << "do Task1 work:" << QThread::currentThreadId();
}
};
class Task2 : public QRunnable
{
public:
Task2()
{ }
virtual ~Task2() override
{
qDebug() << "~Task2()";
}
virtual void run() override
{
qDebug() << "do Task2 work:" << QThread::currentThreadId();
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Task1* task1 = new Task1();
Task2* task2 = new Task2();
QThreadPool threadPool;
threadPool.start(task1);
threadPool.start(task2);
threadPool.waitForDone();
return a.exec();
}
从QRunnable派生出2个任务类Task1、Task2表示2个任务,重写基类run()实现各任务执行的操作。
将2个任务对象通过QThreadPool的start方法传入线程池,表示将任务加入到线程池中,待线程进行处理,线程会调用2个任务的run()。
waitForDone()表示等待,直到所有任务处理完毕,才返回。
运行结果:
可以看到2个任务的run()分别在不同的线程中被调用,并行完成处理,处理完后,线程池默认删除这2个任务。
以上就是QThreadPool的基本使用。
2、QThreadPool其他相关方法
下面看看该类还有哪些方法,QThreadPool相关方法声明如下:
class Q_CORE_EXPORT QThreadPool : public QObject
{
public:
static QThreadPool *globalInstance();
void start(QRunnable *runnable, int priority = 0);
bool tryStart(QRunnable *runnable);
int expiryTimeout() const;
void setExpiryTimeout(int expiryTimeout);
int maxThreadCount() const;
void setMaxThreadCount(int maxThreadCount);
int activeThreadCount() const;
void setStackSize(uint stackSize);
uint stackSize() const;
void reserveThread();
void releaseThread();
bool waitForDone(int msecs = -1);
void clear();
bool tryTake(QRunnable *runnable);
};
方法 | 功能 |
---|---|
globalInstance() | 返回程序默认的全局内存池实例 |
start(QRunnable *runnable, int priority = 0) | 预定一个线程用于执行QRunnable接口,当预定的线程数量超出线程池的最大线程数后,QRunnable接口将会进入队列,等有空闲线程后,再执行 |
tryStart(QRunnable *runnable) | 试图预定一个线程来运行runnable,如果在调用时没有空闲线程,则此函数不执行任何操作并返回false。否则,使用一个可用线程立即运行runnable,该函数返回true。 |
expiryTimeout() | 超过此时间未使用的线程被认为已经过期并将退出,默认为30s。 |
setExpiryTimeout(int expiryTimeout) | 设置线程过期时间,超过此时间未使用的线程将退出。建议在创建线程池之后,但在调用start()之前设置expiryTimeout。 |
maxThreadCount() | 返回线程池维护的最大线程数量 |
setMaxThreadCount(int maxThreadCount) | 设置线程池维护的最大线程数量 |
activeThreadCount() | 返回线程池中激活线程的数量 |
setStackSize(uint stackSize) | 设置线程池中线程的堆栈大小 |
stackSize() | 返回线程池中线程的堆栈大小 |
reserveThread() | 保留一个线程,不考虑activeThreadCount()和maxThreadCount()。一旦你完成了线程,调用releaseThread()来允许它被重用。注意:这个函数总是会增加活动线程的数量。这意味着,通过使用这个函数,activeThreadCount()可以返回一个大于maxThreadCount()的值。 |
releaseThread() | 释放以前通过调用reserveThread()预约的线程。如果不先预约一个线程,调用这个函数会临时增加maxThreadCount()。当线程进入休眠等待时,能够允许其他线程继续。要记得在完成等待时调用reserveThread(),以便线程池可以正确控制activeThreadCount()。 |
waitForDone(int msecs = -1) | 等待,直到所有任务处理完毕,才返回。 |
clear() | 从队列中删除尚未启动的任务。runnable为true的任务会被删除。 |
tryTake(QRunnable *runnable) | 如果runnable任务还没开始运行,那么从队列中删除此runable任务,此时函数返回true;如果runnable任务已经运行,返回false。只能用来删除runnable->autoDelete() == false的runnable任务,否则可能会删错任务。 |
本篇文章来源于微信公众号: 超哥学编程
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/10828.html