.NET异步编程:IO完成端口与BeginRead

    【IT168 专稿】写这个系列原本的想法是讨论一下.NET中异步编程风格的变化,特别是F#中的异步工作流以及未来的.NET 5.0中的基于任务的异步编程模型。但经过前几篇文章(为什么需要异步传统的异步编程使用CPS及yield实现异步)的发表后,很多人对IO异步背后实现的原理以及为什么这样能提高性能很感兴趣。其实我本不想花更多的文字在这些底层实现的细节上,一来我并不擅长这些方面,二来我们使用.NET的异步IO就不需要关心这些底层东西,因为已经为你封装完备了。不过为了避免大家一再在这上面商讨,我还是在这个系列中间插入了一篇来解释一下。

  本文我将从内核对象IO完成端口开始介绍,然后来瞧瞧.NET BCL中的FileStream.BeginRead是如何利用IO完成端口来实现的。

  IO完成端口(IO Completion Port)

  大多数人应该或多或少地听说过IO完成端口这么个东西,而且也知道它是实现高性能IO,高伸缩性应用的尚方宝剑。IO完成端口是一个非常复杂的内核对象,其实现的也非常巧妙,细细琢磨还是非常有意思的。

  创建高伸缩性的应用的一个基本原则就是:创建更少的线程。线程数更少首先消耗的资源就少,每个线程的创建除了要浪费CPU时间外,还要创建一系列的数据结构用来保存线程相关的一些信息:用户栈,线程上下文,内核栈等。这个总共加起来大概1.5M左右,那么你算算你的32位机器总共能使用多少内存?那么对应地能创建多少线程?可能有人讲那对于64位的就无所谓了。嗯,在资源占用这方面64位确实不用担心。但是系统中可运行的线程数越多,你的CPU数又是有限的(8个?80个?)。Windows的任务调度机制是每个线程会运行一个时间片,然后Windows抢占式的调度另一个线程运行。那么线程数越多,Windows势必要进行更频繁的线程上下文切换。线程上下文切换对系统性能的影响在这里我就不多说了,你可以搜搜资料。

  那么如何做到创建更少的线程,而又干更多的事儿呢?答案就是“不等待”。相对CPU来说,IO设备的速度简直低的要命。就好像飞机和拖拉机的差别一样,我们可不能让拖拉机拖了飞机的后退儿。而IO完成端口就是为了这个而生的:创建更少的线程,干更多的事儿。

  IO完成端口首先不是一个我们看得见摸得着的什么插口,也和我们常说的80这样的端口不同。你可以将其理解为一个数据结构或一个对象(下面我会用C#的代码来辅助讲解IO完成端口,仅仅是讲解,这些代码并不是真实的实现):

  Windows提供了一个CreateIoCompletionPort API来创建IO完成端口,实际上这个API有两个作用:创建IO完成端口和将一个IO设备与该端口绑定。创建IO完成端口时有一个很重要的参数:指定同时最多能有多少个线程并行运行,这就是为了保证更少的线程,如果你将这个数值指定为0,那么默认值就会是你机器的CPU数。IO端口里还有一个IO设备句柄列表,你可以将很多设备句柄与这个端口绑定(文件、Socket等):

#div_code img{border:0px;}<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--&gt // 函数原型
HANDLE CreateIoCompletionPort(
    
// 设备句柄
    HANDLE     hFile,
    
// 已有的IO完成端口句柄,如果这里已经指定,则是将前面指定的设备与该端口绑定
    HANDLE     hExistingCompletionPort,
    
// 因为一个IO完成端口可以绑定很多设备,可以用这个来区分
    ULONG_PTR  CompletionKey,
    
// 允许同时运行的线程数
    DWORD      dwNumberOfConcurrentThreads
);
// 创建一个IO完成端口
HANDLE hIoPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL, 0 , 2 );
// 创建文件,如果要异步访问文件则需要指定FILE_FLAG_OVERLAPPED
HANDLE hFile = CreateFile(..);
// 将上面创建的文件句柄与刚才创建的IO完成端口绑定,不仅仅是文件可以
CreateIoCompletionPort(hFile,hIoPort, 1 , 2 );

  除此之外,我们还要为该端口创建一些供使用的线程。然后让这些线程调用Windows提供的GetQueuedCompletionStatus方法。这些线程调用了该方法后会被放到IO完成端口另外一个数据结构中:一个后进先出的队列(我们将其称为等待队列吧)。然后该线程会休眠起来,不占用CPU。然后我们可以调用像ReadFile这样的方法发起一个IO请求:

#div_code img{border:0px;}<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--&gt BOOL ReadFile(
    HANDLE hFile,
    PVOID  pvBuffer,
    DWORD  nNumBytesToRead,
    PDWORD pdwNumBytes,
    OVERLAPPED
* pOverlapped);

ReadFile(..
& overlapped);

转载于:http://blog.itpub.net/25436212/viewspace-690376/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值