IOCP异步优化

一、内存操作和IO操作

在计算机运行执行程序的世界里,从如何得到处理结果分成两大类:

1. 内存操作: CPU在内存里面完成计算,然后得到处理结果。

2. IO操作: CPU会把内存中的程序委托给其他的网络、磁盘等驱动程序,让这些外部的驱动程序来进行具体的处理,处理完成以后再返回给内存程序。对于这两类操作的优化方式是不一样的。内存操作的特点是占用CPU资源,CPU不断的计算。对于内存密集型的操作(Compute-Bound Operation)的优化,我们可以把一个大任务拆分成多个互不影响的子任务,那么就能让多个CPU同时参与运算,最后合并子任务的结果,所花的时间自然就少了。所以内存密集型的操作(Compute-Bound Operation)的优化有一个前提:超线程、多核、甚至是真正的多个CPU的计算机能够同时运行多个线程,对于只有一个CPU的计算机不适合。多线程之间的状态切换是需要额外的CPU资源的。IO操作的特点是基本不占用CPU资源,但是它会占用当前的工作者线程,并使其进入等待状态,等待IO完成的处理结果,然后在继续执行。但是在ASP.NET这种天然多线程的环境里,CLR线程池容量是有上限的,这个上限也代表了应用程序最多可以同时执行的请求数量。如果我们CLR线程池的所有线程都进入了IO等待状态,当再有新用户进来,我们的服务就停止响应了。目前我们IO操作的缺点是当前工作者线程同步等待IO,任何IO处理都会霸占一条工作者线程。所以对于IO密集型的操作(IO-Bound Operation)的优化,我们的思路是使用IOCP(I/O Completion Port)。IOCP翻译了中文是IO完成端口,它是一种异步形态,原理是这样的:当前工作者线程在进行IO处理时,委托给某个设备驱动程序,然后自己返回线程池,当IO完成后,OS会通过IOCP提醒CLR它工作已经完成,当CLR接收到通知后,会唤醒一个I/O线程并且运行用户的回调。

I/O线程:是CLR线程池中预先保留出来的部分线程,这部分线程的作用是为了分发从IOCP中的回调。I/O线程由CLR调用。所以通常情况下,开发者并不会直接用到它。工作者线程和I/O线程区别:它们都是普通的线程,但是CLR线程池中区分它们的目的是为了避免线程都去处理I/O回调而被耗尽,从而引发死锁。关于CLR线程池的细节可以看:线程池的作用和CLR线程池

二、IOCP异步优化

ASP.NET天生就是多线程的运行环境,所以内存密集型的操作(Compute-Bound Operation),我们推荐单线程运算为原则。如果我们在具体业务逻辑里运用了多线程,也意味着系统将对多线程之间的状态切换产生额外的开销。从而加重了服务器的负担。

在IO密集型的操作(IO-Bound Operation)中,我们推荐使用IOCP模式。当执行I/O操作的时候,无论是同步I/O操作还是异步I/O操作,都会调用的Windows的API方法,比如,当读取文件的时候,调用ReadFile函数。该方法会将你的当前线程从用户态转变成内核态,会生成一个I/O请求包,并且初始化这个请求包,这个包中包含一个文件句柄,一个偏移量和一个Byte[]数组。ReadFile会向内核传递,根据这个请求包,windows内核知道需要将这个I/O操作发送给哪个硬件设备。这些I/O操作会进入设备自己的处理队列中,该队列由这个设备的驱动程序维护。

如果此时是同步I/O操作,那么在硬件设备操作I/O的时候,发出I/O请求的线程由于无事可做被windows变成睡眠状态,当硬件设备完成操作后,再唤醒这个线程。这种方式非常直接,但是性能不高,如果请求数很多,那么休眠的线程数也很多,浪费了大量资源。

如果是异步I/O操作,.Net中异步的I/O操作为BeginXXX的形式。该方法在Windows把I/O请求包发送到设备的处理队列后就返回了。同时,在调用异步I/O操作的时候,即调用BeginXXX方法的时候,需要传入一个委托,该委托方法会随着I/O请求包一路传递到设备的驱动程序。在设备处理完I/O请求包后,将该委托再放到CLR线程池中的I/O线程队列里。之前说到过,在CLR内部维护了一个IOCP(I/O completion port),它提供了处理多个异步I/O请求的线程模型,可以把这个IOCP看做是一个消息队列,当一个进程创建了一个IOCP,即创建了一个队列。当异步I/O请 求完成时,设备驱动程序就会生成一个I/O完成包,将它按照FIFO方式排队列入该完成端口。之后,会由I/O线程提取完成I/O请求包,并调用之前的委托。注意:异步调用服务时,回调函数都是运行于CLR线程池的I/O线程当中。

具体的在.NET的代码实例:

static void Main(string[] args)
{
    WebRequest request = HttpWebRequest.Create("http://www.baidu.com");
    request.BeginGetResponse(HandleAsyncCallback, request);
}
static void HandleAsyncCallback(IAsyncResult ar)
{
    WebRequest request = (WebRequest)ar.AsyncState;
    WebResponse response = request.EndGetResponse(ar);
    // more operations...
}

IOCP中有2个队列,一个是先进先出的队列,存放的是IO完成包,即已经完成的IO操作需要执行回调方法,因此先进先出的方式是非常公平的。

还有一个队列是线程队列,IOCP会预分配一些线程在这个队列中,这样会比即时创建线程处理I/O请求速度更快。这个队列是后进先出的,好处是下一个请求的到来可能还是用之前的线程来处理,就不需要进行线程上下文切换,提高了性能。

在《Pro .NET Performance》中有如下一个示意图:

本文分享自微信公众号 - 明丰随笔(liumingfengwx2)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-05-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏开发架构二三事

disruptor源码分析二之RingBuffer初始化流程

MultiProducerSequencer父接口Sequencer的类及相关类与方法:

13020
来自专栏Android必知必会

Android:Field can be converted to a local varible.

版权声明:本文为[他叫自己Mr.张]的原创文章,转载请注明出...

12030
来自专栏Android必知必会

Android必知必会-发布开源 Android 项目注意事项

版权声明:本文为[他叫自己Mr.张]的原创文章,转载请...

10920
来自专栏Java成长之路

Big O notation 算法复杂度计算方法

Big O notation大零符号一般用于描述算法的复杂程度,比如执行的时间或占用内存(磁盘)的空间等,特指最坏时的情形。

18030
来自专栏Android必知必会

Android项目开发填坑记-Fragment的onAttach

版权声明:本文为[他叫自己Mr.张]的原创文章,转载请注明出...

11120
来自专栏前端词典

[译] 如何写出漂亮的 JavaScript 代码

这是一条在软件工程领域流传久远的规则。严格遵守这条规则会让你的代码可读性更好,也更容易重构。如果违反这个规则,那么代码会很难被测试或者重用。

8730
来自专栏大龄程序员的人工智能之路

使用Tensorflow构建属于自己的图片分类器

近几年火热的AI领域吸引了众多有志之士加入,在一段时间的学习之后,不知道你是否有一个疑惑:我能够用AI来做点什么呢?

12860
来自专栏开发架构二三事

浅析内存屏障以及在java中的应用

程序在运行时内存实际的访问顺序和程序代码编写的访问顺序不一定一致,这就是内存乱序访问。内存乱序访问行为出现的理由是为了提升程序运行时的性能。这种内存乱序问题主要...

50940
来自专栏音视频技术

衡量视频质量有哪些指标和工具?

https://www.streamingmedia.com/Articles/Editorial/Featured-Articles/Buyers-Guide...

53330
来自专栏大龄程序员的人工智能之路

有了TensorFlow.js,浏览器中也可以实时人体姿势估计

与谷歌创意实验室合作,我很高兴地宣布发布TensorFlow.js版本的PoseNet,这是一种机器学习模型,允许在浏览器中进行实时人体姿势估计。您可以访问ht...

10610

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励