欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C# 多线程猜想

程序员文章站 2022-05-27 18:58:07
公司分配给我一个活,让我给Kong网关做一个获取设置的站点。Kong网关号称几万的QPS的神器,我有点慌,如果因为我的站点拖累了Kong我就是千古罪人。 配合Kong的站点必须要经过性能测试,在性能测试的时候就发现个很有意思的现象,如果我用25条线程压我的站点,那么结果是这样的。 如果我用50条线程 ......

公司分配给我一个活,让我给kong网关做一个获取设置的站点。kong网关号称几万的qps的神器,我有点慌,如果因为我的站点拖累了kong我就是千古罪人。

配合kong的站点必须要经过性能测试,在性能测试的时候就发现个很有意思的现象,如果我用25条线程压我的站点,那么结果是这样的。

C# 多线程猜想

 

 如果我用50条线程去压站点,结果是这样的

C# 多线程猜想

 

 现象就是,我提高了并发数量,我的qps其实并没有什么变化,但是我的单次平均响应时间缺提高了一倍。其实这种现象还是比较好解释的。首先,我们来了解一下,iis的大概处理逻辑。

C# 多线程猜想

 

其实iis维护了这么几个东西,首先一个是队列,用来提高服务器的同时处理请求数用的。这么说吧,假设我现在程序很原始很简陋,我一次只能处理一条请求,那么,我在处理一条请求的过程中,第二条请求过来了,那么这个时候我显然不应该告诉他,我现在正忙,没空搭理他,而应该是告诉他,你先等会,我马上来处理你。让他等会,其实就是相当于把他放到队列里边,一会再来处理。

另外一个概念叫做,同时处理数。刚才我的假设是我一次只能处理一条数据。但是我存在多个核,就算是一核在一个时间点上只能处理一条数据,那么,现在我机器是4核的,那么我最起码也应该能处理4条数据,假设现在一次性来了4条数据,那么这4条数据基本上可以认为是同时在处理的,但是如果同时来了8条数据,那么就是4条在处理,4条在等待。

现在来解释一下,为什么会出现50并发比25并发,提升了等待时间,但是qps并没有提高。我想可以这么解释,其实qps在25并发的时候已经接近于极限了,这个极限应该怎么算呢,大概就应该是1秒 * 同时处理数 / 每个请求的真实处理时间。可以看出来这个极限其实跟客户端的并发数没有什么直接联系。那么50并发的时候,为什么等待的时间反而变长了呢?那是因为,客户端并发数大于服务器同时处理数的时候,有一部分固定数量的请求在请求队列里,他必须等待已经进入处理逻辑的部分处理完,然后再处理自己,所以就造成了qps并没有提升但是响应时间变长的现象发生。

因为是这样的多倍叠加的模式,所以,有时候,你会发现,你的接口,如果只是几毫秒响应的话,大家都很快。但是一旦你慢下来,响应时间是成指数级的增长。原因也很简单,主要有以下几个。

  • 等待的队列边长了(因为前边处理的很慢,所以等的人越来越多)
  • 等待的单次边长了(但是变长的人不止是你自己呀,还有你等待的其他人)

这几个情况一综合那可不是乘法运算嘛,那可不就是指数级增加嘛。

 

提问,那么究竟多少并发,才是最理想的状态呢?

之前考虑这个问题的时候,可能理所当然的认为,这个东西嘛,应该是跟cpu核数有关系,应该跟核数一样多就是最优解了吧。但是现实经常啪啪打脸。经过实测,一般是要比cpu核数多少不少才是cpu不累,处理效率很高的状态。那么为什么会出现这种情况呢?我觉得这个问题有点大,我们需要拆开来看。

 

1、一个核心真的是一条线程执行的最快吗?

这个问题嘛,其实也对,也不对。说他对视因为,其实如果存在多条线程,那么多条线程之间切换的时候,其实也挺消耗资源的。但是多线程的意义是什么呢?我觉得这个问题也可以拆成两个问题。在拆问题之前先给介绍两个概念。计算密集型、io密集型,计算密集型就是你在做运算,加减乘除也好,比对也要,加密解密也好,这种主要依赖于cpu叫计算密集型线程。如果你的线程大部分时间都消耗在了读取网络数据,读取本地数据,或者驱动硬件等待返回这种情况叫做io密集型。

1.1 一个核心真的是一个计算密集型最快吗?

是的。因为线程本身也是需要消耗资源的,频繁的切换其实对于计算密集型线程没有任何好处,因为计算量并没有变少反而变多了。

1.2 一个核心真的是一个io密集型线程最快吗?

不对。多个io密集型线程肯定比一个io密集型线程要快,因为大部分时间,其实跟cpu没有关系,cpu大部分时间都是在等而已。所以让cpu一次性处理多个,反而更加占有优势。

 

2、为什么不是并发量跟同时处理数相等时是最优解。

真实的业务场景,一条线程并不是纯粹的io或者计算,更多的时候是处于两者都有的情况。那么对于这种线程的话。反正不是一核一个最快,因为它毕竟是存在io的情况。他们肯定要多处理几个才划算。

这样服务器等待客户端请求的时间就太长了,如果并发数量跟处理数量相等的话,那么对于一个并发来说,就相当于客户端发起请求、发送网络数据、服务器处理、发送网络数据、客户端接收网络数据,然后进行下一轮处理。这样的话就相当于客户端与服务器端处于同一个线程,单线程工作,并且中间存在了大量的等待的时间,所以服务器的qps并不会上来。

 

最理想的状态应该是,以下的状态

  • 等待队列中始终存在数据(不会让处理线程等待客户端请求)
  • 客户端的请求进入等待队列后立马被处理(不会因为别的请求而造成响应时间过长,而引发下一步的等待队列过长)

根据上边总结的多线程的相关结论,一般一个核心肯定要处理多个线程,并且等待队列中存在并且存在不了多少数据。

那么最佳并发的结论应该是,核心数 * n(单核心同时处理线程数) + m(等待队列中存在的少数请求)。

 

题外话:为什么golang号称利用协成能够更好的利用cpu 达到更高的运算效率呢?

我猜应该是将io型线程中的多线程切换部分性能节省下来,用作于更多的cpu计算来提高了整体性能。