代码改变世界

I/O限制异步操作-Windows是如何执行I/O操作(CLR)

2011-12-18 11:26  木木子  阅读(629)  评论(0编辑  收藏  举报

 

image(copy from 《CLR via C#》Chapter 27)

如图所示,用FileStream访问磁盘文件,然后用FileStream's Read方法从该File读取Data。当调用FileStream's Read方法时,当期线程将托管Code转换为本地/用户代码,调用Win32的ReadFile方法(#1)。在Win32的ReadFile方法中初始化一个结构体叫做IRP(I/O Request Packet)#2。IRP内包含了File的句柄,偏移量,Data存储地址等等。

Win32的FileStream接下来调用Windows的核心代码,并传入IRP。根据IRP中的句柄,将IRP放入相应的设备IRP队列,然后硬件设备执行I/O操作。

当硬件设备执行I/O操作时,你的当前线程是睡眠状态,这样有一个好处是不会浪费CPU时间,但是占用了memory[占着茅坑不拉屎],还有用户堆栈,核心堆栈,线程环境块(TEB),其他数据结构。

最后,该硬件设备完成了I/O操作,同时对应的线程被唤醒,被CPU调度,从核心模式回到用户模式,再回到托管代码,。FileStream's Read返回一个Int32,表示从File中读取的字节数。

假设你实现了一个网页应用程序,每个用户登入这个应用程序就会同步访问数据库,这样就会一个用户就产生一次数据库访问。先是一个用户登入,线程池线程就会调用托管代码,该线程等待数据库反馈,会阻塞一个不定的时间。假如,同时有一个用户登入,又创建一个线程,并等待数据库反馈而被阻塞。越来愈多的用户登入,越来越多的线程产生,并且全部阻塞。这样导致一个坏结果:你的Wed服务有大量的系统资源被浪费。

更坏得考虑,数据库返回了所有结果,所有线程都被解锁并开始执行。但是,你执行的线程数量远远大于CPU核心数量。这样Windows产生频繁的上下文调度,性能大打折扣。这不是一个可伸缩性的应用程序。

接下来,考虑下WIndows的异步I/O操作。

image(copy from 《CLR via C#》Chapter 27)

在构造FileStream实例时,传入FileOption.Asynchronous标志,表示Windows会异步执行Read和Write操作。

用BeginRead方法代替了Read。过程大体上跟调用Read方法一样,但在IRP进入IRP队列时,该线程没有阻塞,而是返回到BeginRead,这时候,IRP当然还没用被执行完,所以在Begin之后,不能马上读取数据。

那怎么知道什么时候IRP才会被执行完并调用数据呢?在调用BeginRead方法时候,会传入一个CallbackMethod的委托,该CallbackMethod会与IRP关联,当硬件设备完成IRP,线程池就会调用相应的Call Back方法,即传入的CallbackMethod。

再次回到上个Web应用程序中,用异步操作改进这个程序。当一个用户登入,Web服务器产生一个异步线程执行数据库访问,这样,线程不会被堵塞,当又登入一个用户,同一个线程执行数据库访问。在数据库反馈时,同样又线程池操作。这样,就可以使用一个或少量的线程执行大量的哟过户访问,并且将数据反馈给用户。这样,服务器反应速度快,并且没用上下文调度,这才是个可伸缩性的成用程序。