c++线程池_⾯试官:⾼并发下,你怎么选择最优的线程数?为了加快程序处理速度,我们会将问题分解成若⼲个并发执⾏的任务。并且创建线程池,将任务委派给线程池中的线程,以便使它们可以并发的执⾏。在⾼并发的情况下采⽤线程池,可以有效降低线程创建释放的时间花销及资源开销,如不使⽤线程池,有可能造成系统创建⼤量线程⽽导致消耗完系统内存以及“过度切换”(在JVM中采⽤的处理机制为时间的轮转,减少了线程间的相互切换) 。
但是有⼀个很⼤的问题摆在我们⾯前,即我们希望尽可能多地创建任务,但由于资源所限我们⼜不能创建过多的线程。那么在⾼并发的情况下,我们怎么选择最优的线程数量呢?选择原则⼜是什么呢?
⼀、理论分析
关于如何计算并发线程数,有两种说法。
第⼀种,《Java Concurrency in Practice》即《java并发编程实践》8.2节 170页
对于计算密集型的任务,⼀个有Ncpu个处理器的系统通常通过使⽤⼀个Ncpu + 1个线程的线程池来获得最优的利⽤率(计算密集型的线程恰好在某时因为发⽣⼀个页错误或者因其他原因⽽暂停,刚好有⼀个“额外”的线程,可以确保在这种情况下CPU周期不会中断⼯作)。
对于包含了 I/O和其他阻塞操作的任务,不是所有的线程都会在所有的时间被调度,因此你需要⼀个更⼤
的池。为了正确地设置线程池的长度,你必须估算出任务花在等待的时间与⽤来计算的时间的⽐率;这个估算值不必⼗分精确,⽽且可以通过⼀些监控⼯具获得。你还可以选择另⼀种⽅法来调节线程池的⼤⼩,在⼀个基准负载下,使⽤ ⼏种不同⼤⼩的线程池运⾏你的应⽤程序,并观察CPU利⽤率的⽔平。
给定下列定义:
你可以使⽤Runtime来获得CPU的数⽬:
int N_CPUS = Runtime().availableProcessors();
当然,CPU周期并不是唯⼀你可以使⽤线程池管理的资源。其他可以约束资源池⼤⼩的资源包括:内存、⽂件句柄、套接字句柄和数据库连接等。计算这些类型资源池的⼤⼩约束⾮常简单:⾸先累加出每⼀个任务需要的这些资源的总童,然后除以可⽤的总量。所得的结果是池⼤⼩的上限。
当任务需要使⽤池化的资源时,⽐如数据库连接,那么线程池的长度和资源池的长度会相互影响。如果每⼀个任务都需要⼀个数据库连接,那么连接池的⼤⼩就限制了线程池的有效⼤⼩;类似地,当线程池
中的任务是连接池的唯⼀消费者时,那么线程池的⼤⼩反⽽⼜会限制了连接池的有效⼤⼩。
如上,在《Java Concurrency in Practice》⼀书中,给出了估算线程池⼤⼩的公式:
第⼆种,《Programming Concurrency on the JVM Mastering》即《Java 虚拟机并发编程》2.1节 12页
为了解决上述难题,我们希望⾄少可以创建处理器核⼼数那么多个线程。这就保证了有尽可能多地处理器核⼼可以投⼊到解决问题的⼯作中去。通过下⾯的代码,我们可以很容易地获取到系统可⽤的处理器核⼼数:
所以,应⽤程序的最⼩线程数应该等于可⽤的处理器核数。如果所有的任务都是计算密集型的,则创建处理器可⽤核⼼数那么多个线程就可以了。在这种情况下,创建更多的线程对程序性能⽽⾔反⽽是不利的。因为当有多个仟务处于就绪状态时,处理器核⼼需要在线程间频繁进⾏上下⽂切换,⽽这种切换对程序性能损耗较⼤。但如果任务都是IO密集型的,那么我们就需要开更多的线程来提⾼性能。
当⼀个任务执⾏IO操作时,其线程将被阻塞,于是处理器可以⽴即进⾏上下⽂切换以便处理其他就绪线程。如果我们只有处理器可⽤核⼼数那么多个线程的话,则即使有待执⾏的任务也⽆法处理,因为我们已经拿不出更多的线程供处理器调度了。
如果任务有50%的时间处于阻塞状态,则程序所需线程数为处理器可⽤核⼼数的两倍。如果任务被阻塞的时间少于50%,即这些任务是计算密集型的,则程序所需线程数将随之减少,但最少也不应低于处理器的核⼼数。如果任务被阻塞的时间⼤于执⾏时间,即该任务是IO密集型的,我们就需要创建⽐处理器核⼼数⼤⼏倍数量的线程。我们可以计算出程序所需线程的总数,总结如下:
线程数 = CPU可⽤核⼼数/(1 - 阻塞系数),其中阻塞系数的取值在0和1之间。jvm面试题总结及答案
计算密集型任务的阻塞系数为0,⽽IO密集型任务的阻塞系数则接近1。⼀个完全阻塞的任务是注定要挂掉的,所以我们⽆须担⼼阻塞系数会达到1。
为了更好地确定程序所需线程数,我们需要知道下⾯两个关键参数:
处理器可⽤核⼼数;
任务的阻塞系数;
第⼀个参数很容易确定,我们甚⾄可以⽤之前的⽅法在运⾏时查到这个值。但确定阻塞系数就稍微困难⼀些。我们可以先试着猜测,抑或采⽤⼀些性能分析⼯具或java.lang.management API来确定线程化在系统IO操作上的时间与CPU密集任务所耗时间的⽐值。如上,在《Programming Concurrency on the JVM Mastering》⼀书中,给出了估算线程池⼤⼩的公式:
线程数 = Ncpu /(1 - 阻塞系数)
对于说法⼀,假设CPU 100%运转,即撇开CPU使⽤率这个因素,线程数 = Ncpu x (1 + W/C)。
现在假设将⽅法⼆的公式等于⽅法⼀公式,即Ncpu /(1 - 阻塞系数)= Ncpu x (1 + W/C),推导出:阻塞系数 = W / (W + C),即阻塞系数 = 阻塞时间 /(阻塞时间 + 计算时间),这个结论在⽅法⼆后续中得到印证,如下:
由于对Web服务的请求⼤部分时间都花在等待服务器响应上了,所以阻塞系数会相当⾼,因此程序需要开的线程数可能是处理器核⼼数的若⼲倍。假设阻塞系数是0.9,即每个任务90%的时间处于阻塞状态⽽只有10%的时间在⼲活,则在双核处理器上我们就需要开20个线程(使⽤第2.1节的公式计算)。如果有很多只股票要处理的话,我们可以在8核处理器上开到80个线程来处理该任务。
由此可见,说法⼀和说法⼆其实是⼀个公式。
⼆、实际应⽤
那么实际使⽤中并发线程数如何设置呢?我们先看⼀道题⽬:
假设要求⼀个系统的TPS(Transaction Per Second或者Task Per Second)⾄少为20,然后假设每个Transaction由⼀个线程完成,继续假设平均每个线程处理⼀个Transaction的时间为4s。那么问题转化为:
如何设计线程池⼤⼩,使得可以在1s内处理完20个Transaction?
计算过程很简单,每个线程的处理能⼒为0.25TPS,那么要达到20TPS,显然需要20/0.25=80个线程。
这个理论上成⽴的,但是实际情况中,⼀个系统最快的部分是CPU,所以决定⼀个系统吞吐量上限的是CPU。增强CPU处理能⼒,可以提⾼系统吞吐量上限。在考虑时需要把CPU吞吐量加进去。
分析如下(我们以说法⼀公式为例):
Nthreads = Ncpu x (1 + W/C)
即线程等待时间所占⽐例越⾼,需要越多线程。线程CPU时间所占⽐例越⾼,需要越少线程。这就可以划分成两种任务类型:
IO密集型 ⼀般情况下,如果存在IO,那么肯定W/C > 1(阻塞耗时⼀般都是计算耗时的很多倍),但是需要考虑系统内存有限(每开启⼀个线程都需要内存空间),这⾥需要在服务器上测试具体多少个线程数适合(CPU占⽐、线程数、总耗时、内存消耗)。如果不想去测试,保守点取1即可,Nthreads = Ncpu x (1 + 1) = 2Ncpu。这样设置⼀般都OK。
计算密集型 假设没有等待W = 0,则W/C = 0。Nthreads = Ncpu。
根据短板效应,真实的系统吞吐量并不能单纯根据CPU来计算。那要提⾼系统吞吐量,就需要从“系统短板”(⽐如⽹络延迟、IO)着⼿:尽量提⾼短板操作的并⾏化⽐率,⽐如多线程下载技术;
增强短板能⼒,⽐如⽤NIO替代IO;
第⼀条可以联系到Amdahl定律,这条定律定义了串⾏系统并⾏化后的加速⽐计算公式:加速⽐ = 优化前系统耗时 / 优化后系统耗时 加速⽐越⼤,表明系统并⾏化的优化效果越好。Addahl定律还给出了系统并⾏度、CPU数⽬和加速⽐的关系,加速⽐为Speedup,系统串⾏化⽐率(指串⾏执⾏代码所占⽐率)为F,CPU数⽬为N:Speedup <= 1 / (F + (1-F)/N)
当N⾜够⼤时,串⾏化⽐率F越⼩,加速⽐Speedup越⼤。
这时候⼜抛出是否线程池⼀定⽐单线程⾼效的问题?
答案是否定的,⽐如Redis就是单线程的,但它却⾮常⾼效,基本操作都能达到⼗万量级/s。从线程这个⾓度来看,部分原因在于:
多线程带来线程上下⽂切换开销,单线程就没有这种开销;
锁;
当然“Redis很快”更本质的原因在于:
Redis基本都是内存操作,这种情况下单线程可以很⾼效地利⽤CPU。⽽多线程适⽤场景⼀般是:存在相当⽐例的IO和⽹络操作。
总的来说,应⽤情况不同,采取多线程/单线程策略不同;线程池情况下,不同的估算,⽬的和出发点是⼀致的。
⾄此结论为:
IO密集型 = 2Ncpu(可以测试后⾃⼰控制⼤⼩,2Ncpu⼀般没问题)(常出现于线程中:数据库数据交互、⽂件上传下载、⽹络数据传输等等)
计算密集型 = Ncpu(常出现于线程中:复杂算法)
当然说法⼀中还有⼀种说法:
对于计算密集型的任务,⼀个有Ncpu个处理器的系统通常通过使⽤⼀个Ncpu + 1个线程的线程池来获得最优的利⽤率(计算密集型的线程恰好在某时因为发⽣⼀个页错误或者因其他原因⽽暂停,刚好有⼀个“额外”的线程,可以确保在这种情况下CPU周期不会中断⼯作)。
即,计算密集型 = Ncpu + 1,但是这种做法导致的多⼀个CPU上下⽂切换是否值得,这⾥不考虑。读者可⾃⼰考量。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。