1.需求的由来Web-Server通常有一个配置,最大工作线程数,后端服务一般都有一个配置,工作线程池的线程数,以及配置这个线程数不同的业务架构师有不同的经验值。有的业务设置为CPU核数的2倍,有的业务设置为CPU核数的8倍,有的业务设置为CPU核数的32倍。“工作线程数”的设置依据是什么,设置多少可以最大化CPU性能,是本文要讨论的问题。2.共同认知在进一步深入讨论之前,我们首先应该通过提问的方式就一些共同认知达成一致。Q:工作线程数是不是越多越好?答:当然不是。服务器的CPU核数是有限的,可以并发的线程数也是有限的。单核CPU设置10000个工作线程是没有意义的。线程切换有开销。如果线程切换过于频繁,反而会降低性能。Q:调用sleep()函数时,线程是否一直占用CPU?答:不会,CPU会在等待其他需要CPU资源的线程时释放。不仅仅是sleep()函数,在进行一些阻塞调用的时候,比如在网络编程中:阻塞accept(),等待客户端连接,阻塞recv(),等待下游数据包返回,不占用CPU资源。Q:单核CPU设置多线程有意义吗?它可以提高并发性能吗?A:即使是单核CPU,使用多线程也是有意义的。在大多数情况下,它还可以提高并发性。多线程编码可以让代码更加清晰。例如:IO线程收发包,Worker线程进行任务处理,Timeout线程进行超时检测。如果有任务一直在占用CPU资源进行计算,此时增加线程并不会增加并发。比如下面的代码会一直占用CPU,使CPU占用率达到100%:while(1){i++;}一般来说,Worker线程一般不会一直占用CPU进行计算。这时候即使CPU是单核,加入Worker线程也可以提高并发性,因为这个线程是在休息的3.公共服务线程模型了解公共服务线程模型有助于理解服务并发的原理。一般来说,网上常见的服务线程模型有两种:IO线程和工地通过任务队列解耦纯异步IO线程和工作线程通过队列解耦类模型如上图所示,大部分Web-Server和服务框架使用这样一个“IO线程和Worker线程通过队列解耦”的线程模型:有几个IO线程监听上游发来的请求,收发数据包(生产者)。有一个或多个任务队列,作为与IO线程和Worker线程异步解耦的数据传输通道(关键资源)。有多个作业线程执行真正的任务(消费者)。这种线程模型被广泛使用,适用于大多数场景。这种线程模型的特点是工作线程是同步阻塞执行任务的(回想一下Java程序是如何在tomcat线程中执行的,任务是如何在dubbo工作线程中执行的),所以可以通过增加并发能力来提高并发能力工作线程数。今天讨论的重点是“这个模型可以设置多少工作线程来达到最大并发”。纯异步线程模型不会阻塞。这种线程模型只需要设置少量的线程就可以达到高吞吐量。这种模式的缺点是:如果使用单线程模式,很难发挥多CPU的优势,多核程序员更习惯于写同步代码。回调方法对代码的可读性有影响,对程序员的要求更高。框架比较复杂,往往需要服务端收发组件、服务端队列、客户端收发组件、客户端队列。上下文管理组件、有限状态机组件、超时管理组件的支持4、工作线程的工作模式了解工作线程的工作模式,对于量化分析线程数的设置很有帮助:上图是一个工作线程的典型流程,从processingstart开始到processingend结束,这个任务的处理一共有7步:从工作队列中取出任务,进行一些本地的初始化计算,比如http协议解析,参数解析,参数校验等。访问缓存获取一些数据获取到缓存中的数据后,进行一些本地的计算。这些计算与业务逻辑有关。通过RPC调用下游服务获取一些数据,或者让下游服务处理一些相关的任务。RPC调用完成后,进行一些本地计算,如何计算和业务逻辑相关的访问DB进行一些数据操作,在操作数据库之后做一些收尾工作,而这些收尾工作也是本地计算,分析整个业务逻辑相关处理的时间线,你会发现:(1)其中,1、第3、5、7步(上图中粉色时间线),线程在执行本地业务逻辑计算时需要占用CPU(2)。在步骤2、4、6(上图中橙色时间线)中,访问缓存、服务、DB进程中的线程处于等待结果的状态,不需要占用CPU。进一步分解,这段“等待结果”的时间分为三部分:请求在网络上传输到下游缓存,Service,DB下游缓存,Service和DB执行任务处理。缓存、服务和数据库将消息上传回网络上的工作线程。五、工作线程数的量化分析与合理设置***下面来回答一下工作线程数应该多合理的问题。通过上面的分析,Worker线程在执行过程中,一部分计算时间需要占用CPU,另一部分等待时间不需要占用CPU。通过量化分析,比如打日志统计,可以统计整个Worker线程执行的过程。两部分时间的比例,例如:执行计算,CPU占用的时间(粉色时间轴)是100ms等待时间,CPU未占用的时间(橙色时间轴)也是100ms结果是该线程的计算和等待时间是1:1,即50%的时间在计算(占用CPU),50%的时间在等待(不占用CPU):假设是单线程核心,此时设置为2个工作线程可以充分利用CPU,让CPU跑到100%。假设此时是N核,那么设置为2N个工位可以充分利用CPU,让CPU跑到N*100%。结论:N核服务器,通过单线程分析业务执行如果本地计算时间为x,等待时间为y,则工作线程数(线程池线程数)设置为N*(x+y)/x,可以最大化CPU利用率。体会:一般来说,对于非CPU密集型业务(加解密、压缩解压、查找排序等业务属于CPU密集型业务),瓶颈在后端数据库访问或RPC调用,本地CPU计算时间很少,所以设置几十或几百个工作线程可以提高吞吐量。6.总结线程数不是越多越好。sleep()不占用CPU。在单核上设置多线程,不仅可以使代码清晰,还可以提高吞吐量。站点和服务最常用的线程模型是“IO线程和工作站点通过任务队列解耦”,此时设置多个工作线程可以提高N核服务器的吞吐量。通过日志分析,任务执行过程中,本地计算时间为x,等待时间为y,则设置工作线程数(线程池线程数)为N*(x+y)/x可以最大化CPU利用率【本文为专栏作者《58神剑》原创稿件,转载请联系原作者】点此阅读更多本作者好文
