• 《Windows核心编程》之“完成端口”(对所有IO都是如此,不仅仅是对socket)


        《Windows核心编程》第10章开头部分一再强调:“IO Completion Port”是“构建高性能、可升缩的应用程序”的最佳设施之一,它不仅适用于处理设备IO,也适用于其它越来越多的应用场景,比如:Job内核对象、Socket编程等。故此,我单独用一篇博客来讲述“IO Completion Port”的应用场景、原理、用法和示例。

    一、缘起

    1,线程通信

        对于一个服务应用程序(service application)来说,典型的操作是对客户请求进行处理,我们无法预见这些客户请求会在何时到达,也无法预见处理这些客户请求需要消耗多少处理器资源。这些操作常常来自诸如网卡、适配器之类的I/O设备,而对这些请求进行处理又经常会用到额外的I/O设备,比如磁盘文件。

        在Windows应用程序中,线程是我们最好的工具,可以用来对工作进行划分。我们可以给每个线程指定 一个处理器,这样在多处理器的机器上就可以同时执行多个操作,从而提高吞吐量。当线程发出一个同步设备I/O请求的时候,它会被临时挂起,直到设备完成I/O请求为止。此类挂起会损害性能,这是因为线程无法进行有用的工作,比如开始对另一个客户请求进行处理。因此,简而言之,我们希望线程不会被阻塞住,这样它们就能始终进行有用的工作。

        为了不让线程闲下来,我们需要让各个线程就它们正在执行的操作互相通信。这类通信机制可以帮助我们创建高性能的应用程序。

        

        异步I/O实际上也是基于线程通信来实现的,如上图。左边的线程执行正常流程,右边的线程处理缓慢耗时的I/O过程;右边的线程处理完I/O后,会通知左边的线程,而左边的线程在运行到一定时候,也会停下来等待和检查是否有属于它的“完成通知”。

        如何管理线程间的通信(或同步),以降低线程的阻塞时间(或等待时间),是设计高性能应用程序的关键。

    2,经典服务器模型

        回顾历史,我们能够采用以下两种模型之一来构架一个服务应用程序。

    1)串行模型(serial model)

        一个线程等待一个客户(通常是通过网络)发出请求。当请求到的时候,线程会被唤醒并对客户请求进行处理。

    优点:简单,非异步。

    缺点:不能同时处理多个请求。

    2)并发模型(concurrent model)

        一个线程等待一个客户请求,并创建一个新的线程来处理请求。当新线程正在处理客户请求的时候,原来的线程会进入下一个循环并等待另一个客户请求。当处理客户请求的线程完成整个处理过程的时候,该线程就会终止。

    优点:能够同时接收多个请求。它相对于“一个等待线程 + 多个处理线程”,等待线程循环运行,随时可以接收客户请求。

        比较串行模型和并发模型,可以它们最大的不同是:串行模型服务应用程序只有一线程(Single thread),而并发模型服务应用程序则是有多少个客户请求,就创建多少个线程(one-thread-per-client)。

    缺点:一是,并发线程过多时,会增加系统的线程切换时间(context switching),降低CPU的有效工作时间。二是,创建和销毁线程需要一定时间开销。

        针对并发模型的两个缺点,微软分别设计了IO完成端口和线程池两种设施,组合使用可以有效替代经典的并发模型。

    二、理论

        IO完成端口与线程池结合,主要用于创建高效的异步IO应用程序。为了理解IO完成端口的用法,我们需要先了解一些相关知识。

    1,重叠结构(OVERLAPPED)

        OVERLAPPED的是异步IO必备的一个数据结构。在《Windows核心编程》10.4.1节专门描述它,如下:

        When performing asynchronous device I/O, you must pass the address to an initialized OVERLAPPED
    structure via the pOverlapped parameter. The word "overlapped" in this context means that the time
    spent performing the I/O request overlaps the time your thread spends performing other tasks.

        通俗点说就是:

    1)OVERLAPPED是ReadFile/WriteFile等异步操作APIs的一个必须的实参(传址方式)。

    2)“overlapped”的意思是执行IO请求的时间与线程执行其他任务的时间是重叠的(overlapped)。

        再来看MSDN的说明:

    Contains information used in asynchronous (or overlapped) input and output (I/O).

    链接:https://msdn.microsoft.com/en-us/library/windows/desktop/ms684342(v=vs.85).aspx

        我们可以将OVERLAPPED理解为“与某个I/O请求关联的附加信息”。也有人将它理解为I/O请求的ID号,但我觉得“附加信息”更正确一点。事实上,在Jeffrey给的FileCopy.exe示例中,他对OVERLAPPED进行了扩展(继承),如下:

    [cpp] view plain copy
     
    1. // Each I/O Request needs an OVERLAPPED structure and a data buffer  
    2. class CIOReq : public OVERLAPPED {  
    3. public:  
    4.    CIOReq() {  
    5.       Internal = InternalHigh = 0;     
    6.       Offset = OffsetHigh = 0;     
    7.       hEvent = NULL;  
    8.       m_nBuffSize = 0;  
    9.       m_pvData = NULL;  
    10.    }  
    11.     ......  
    12. private:  
    13.    SIZE_T m_nBuffSize;  
    14.    PVOID  m_pvData;  
    15. };  


        Jeffrey的这个扩展揭示了“异步IO请求”的本质 —— an OVERLAPPED structure and a data buffer。我们真正需要传输的就是这两个东西,其中OVERLAPPED作为标识,buffer才是真实的信息载体。当然,如果是同步IO,一次只能接收一个请求,就无需OVERLAPPED这个标识物啦,直接传NULL即可。

    注:ReadFile/WriteFile只有一个buffer,但是DeviceIoControl有两个buffer —— 输入buffer和输出buffer。这个buffer是信息的载体,而不是信息,事实上,ReadFile的发出请求的时候,buffer为空,待请求处理完了,系统会将信息填充到该buffer上。

    2,IO完成端口

        IO完成端口是一个内核对象,它的作用是管理关联设备(Associated Devices)的异步IO请求的完成通知。这个话比较拗口,通俗点说,IO完成端口会监视与它管理的设备,如果该设备的某次异步IO请求被处理完了,系统会先把完成通知发给IO完成端口,IO完成端口再将收到的完成通知排队,然后去调度等待列表中的线程来最终收尾一个IO请求。

        要理解IO完成端口的工作原理,就需要先搞清楚它的3个列表和2个队列,见《Windows核心编程》图10-4。

    1)设备列表

        包含所有与该IO完成端口关联的设备。

    2)完成队列(先入先出)

        IO完成端口将收到的系统发给他的IO完成通知存储在完成队列中。

    3)等待线程队列(后入先出)

        存储可供IO完成端口调度(选择)的线程。IO完成端口会在该队列中选择一个线程了处理一项完成通知。

    4)已释放线程列表

        被IO完成端口选中并分派了一项IO完成通知的线程。

    5)已暂停线程列表

        已释放的线程调用了一个函数将自己挂起。也就是说“4)”中某个线程接到了完成通知,并进行了收尾工作,在这个收尾工作中,调用了诸如Sleep函数,将自己挂起了。

        

        从以上的描述可知,IO完成端口维护了多项线程相关的信息,并协调线程共同高效地完成“并发IO请求”。换句话说,IO完成端口是线程通信的调度者。

    3,完成通知(Completion Notification)

        完成通知与IO请求是相对的。IO请求包含的是发送的信息(OVERLAPPED + buffer),完成通知包含的是返回信息。完成通知具体包含的信息有4项:

    1)已传输字节数;

    2)完成键值;

    3)OVERLAPPED指针;

    4)错误码;

    三、流程

        介绍完了IO完成端口的背景知识和基本原理后,下面开始介绍怎么使用IO完成端口。本文按照Jeffrey的思路来介绍IO完成端口的使用流程,另外还有其他使用流程,可以参考我的博客:EchoAPP 和Socket编程之IO完成端口,都配了流程图,它们的使用相对复杂一点。

        Jeffrey为了方便使用,将IO完成端口封装为一个类—— class CIOCP ,它将创建完成端口和绑定设备分为两个步骤,见FileCopy.exe示例。我总结其流程大致如下:

    注:

    1)新开工作线程的数量一般设计为CPU数量的2倍。可以通过Windows API,“GetSystemInfo”来获取CPU信息,参考《Windows核心编程》第14章的“SysInfo.exe”示例。

    2)在线程中开启循环,不让线程函数直接返回,这样可以避免反“复创建和销毁线程”。

    四、示例

        参考我的博客:EchoAPP 和Socket编程之IO完成端口,前者相对要简单一点,是普通设备IO,后者是Socket编程。

    五,知识图片

        我用一张思维导出总结了《Windows核心编程》关于完成端口的知识点,如下:

    http://blog.csdn.net/Sagittarius_Warrior/article/details/52368970

  • 相关阅读:
    Creating a generic Web Parts for hosting ASP.NET User Controls
    Speed Up SQL Server Apps 提高SQL Server应用程序的运行效率 (Part 1)
    How to use CreateChildContorls method inherited from System.Web.UI.Control
    How to quickly access Web Part Management Page
    SQL Script tips for MS SQL Server
    How to enable single signon service on the SPS
    A brief summary of UML & Rational Rose – Use Case Diagram, Part II
    Borland Together for Visual Studio.Net V2.0 安装问题
    Speed Up SQL Server Apps 提高SQL Server应用程序的运行效率 (Part 2)
    体验ReSharper V1.0 for VS.Net 2003 Part I
  • 原文地址:https://www.cnblogs.com/findumars/p/8433005.html
Copyright © 2020-2023  润新知