当前位置:首页 > > 充电吧
[导读]完成端口(I/O completion)原理收藏  异步过程调用(apcs)问题:只有发overlapped请求的线程才可以提供callback函数(需要一个特定的线程为一个特定的I/O请求服务)

完成端口(I/O completion)原理收藏  

异步过程调用(apcs)问题:

只有发overlapped请求的线程才可以提供callback函数(需要一个特定的线程为一个特定的I/O请求服务)。

完成端口(I/O completion)的优点:

不会限制handle个数,可处理成千上万个连接。I/O completion port允许一个线程将一个请求暂时保存下来,由另一个线程为它做实际服务。

并发模型与线程池:

在典型的并发模型中,服务器为每一个客户端创建一个线程,如果很多客户同时请求,则这些线程都是运行的,那么CPU就要一个个切换,CPU花费了更多的时间在线程切换,线程确没得到很多CPU时间。到底应该创建多少个线程比较合适呢,微软件帮助文档上讲应该是2*CPU个。但理想条件下最好线程不要切换,而又能象线程池一样,重复利用。I/O完成端口就是使用了线程池。

理解与使用:

第一步:

在我们使用完成端口之前,要调用CreateIoCompletionPort函数先创建完成端口对象。

定义如下:
HANDLE CreateIoCompletionPort(
                                 HANDLE FileHandle,
                                HANDLE ExistingCompletionPort,
                               DWORD CompletionKey,
                               DWORD NumberOfConcurrentThreads
);
FileHandle:

文件或设备的handle, 如果值为INVALID_HANDLE_VALUE则产生一个没有和任何文件handle有关系的port.( 可以用来和完成端口联系的各种句柄,文件,套接字)

ExistingCompletionPort:

NULL时生成一个新port, 否则handle会加到此port上。

CompletionKey:

用户自定义数值,被交给服务的线程。GetQueuedCompletionStatus函数时我们可以完全得到我们在此联系函数中的完成键(申请的内存块)。在GetQueuedCompletionStatus

中可以完封不动的得到这个内存块,并且使用它。

NumberOfConcurrentThreads:

参数NumberOfConcurrentThreads用来指定在一个完成端口上可以并发的线程数量。理想的情况是,一个处理器上只运行一个线程,这样可以避免线程上下文切换的开销。如果这个参数的值为0,那就是告诉系统线程数与处理器数相同。我们可以用下面的代码来创建I/O完成端口。

隐藏在之创建完成端口的秘密:

1. 创建一个完成端口

CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, dwNumberOfConcurrentThreads);

2. 设备列表,完成端口把它同一个或多个设备相关联。

CreateIoCompletionPort(hDevice, hCompPort, dwCompKey, 0) ;

第二步:

根据处理器个数,创建cpu*2个工作线程:

CreateThread(NULL, 0, ServerWorkerThread, CompletionPort,0, &ThreadID))

与此同时,服务器调用WSASocket,bind, listen, WSAAccept,之后,调用

CreateIoCompletionPort((HANDLE) Accept, CompletionPort... )把一个套接字句柄和一个完成端口绑定到一起。完成端口又同一个或多个设备相关联着,所以以套接字为基础,投递发送和请求,对I/O处理。接着,可以依赖完成端口,接收有关I/O操作完成情况的通知。再看程序里:

WSARecv(Accept, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,

&(PerIoData->Overlapped), NULL)开始调用,这里象前面讲过的一样,既然是异步I/O,所以WSASend和WSARecv的调用会立即返回。

系统处理:

当一个设备的异步I/O请求完成之后,系统会检查该设备是否关联了一个完成端口,如果是,系统就向该完成端口的I/O完成队列中加入完成的I/O请求列。

然后我们需要从这个完成队列中,取出调用后的结果(需要通过一个Overlapped结构来接收调用的结果)。怎么知道这个队列中已经有处理后的结果呢,调用GetQueuedCompletionStatus函数。

工作线程与完成端口:

和异步过程调用不同(在一个Overlapped I/O完成之后,系统调用该回调函数。OS在有信号状态下(设备句柄),才会调用回调函数(可能有很多APCS等待处理了))

GetQueuedCompletionStatus

在工作线程内调用GetQueuedCompletionStatus函数。

GetQueuedCompletionStatus(

    HANDLE CompletionPort,

    LPDWORD lpNumberOfBytesTransferred,

    LPDWORD lpCompletionKey,

    LPOVERLAPPED *lpOverlapped,

    DWORD dwMilliseconds

);

CompletionPort:指出了线程要监视哪一个完成端口。很多服务应用程序只是使用一个I/O完成端口,所有的I/O请求完成以后的通知都将发给该端口。

lpNumberOfBytesTransferred:传输的数据字节数

lpCompletionKey:

完成端口的单句柄数据指针,这个指针将可以得到我们在CreateIoCompletionPort中申请那片内存。

lpOverlapped:

重叠I/O请求结构,这个结构同样是指向我们在重叠请求时所申请的内存块,同时和lpCompletionKey,一样我们也可以利用这个内存块来存储我们要保存的任意数据。

dwMilliseconds:

等待的最长时间(毫秒),如果超时,lpOverlapped被设为NULL,函数返回False.

GetQueuedCompletionStatus功能及隐藏的秘密:

GetQueuedCompletionStatus使调用线程挂起,直到指定的端口的I/O完成队列中出现了一项或直到超时。(I/0完成队列中出现了记录)调用GetQueuedCompletionStatus时,调用线程的ID(cpu*2个线程,每个ServerWorkerThread的线程ID)就被放入该等待线程队列中。

等待线程队列很简单,只是保存了这些线程的ID。完成端口会按照后进先出的原则将一个线程队列的ID放入到释放线程列表中。

这样,I/O完成端口内核对象就知道哪些线程正在等待处理完成的I/O请求。当端口的I/O完成队列出现一项时,完成端口就唤醒(睡眠状态中变为可调度状态)等待线程队列中的一个线程。线程将得到完成I/O项中的信息:传输的字节数,完成键(单句柄数据结构)和Overlapped结构地址,线程是通过GetQueuedCompletionStatus返回这些信息,等待CPU的调度。

GetQueuedCompletionStatus返回可能有多种原因,如果传递无效完成端口句柄,函数返回False,GetLastError返回一个错误(ERROR_INVALID_HANDLE),如果超时,返回False, GetLastError返回WAIT_TIMEOUT, i/o完成队列删除一项,该表项是一个成功完成的I/O请求,则返回True。

调用GetQueuedCompletionStatus的线程是后进先出的方式唤醒的,比如有4个线程等待,如果有一个I/O,最后一个调用GetQueuedCompletionStatus的线程被唤醒来处理。处理完之后,再调用 GetQueuedCompletionStatus进入等待线程队列中。

深入分析完成端口线程池调度原理:

假设我们运行在2CPU的机器上。创建完成端口时指定2个并发,创建了4个工作线程加入线程池中等待完成I/O请求,且完成端口队列(先入先出)中有3个完成I/O的请求的情况:

工作线程运行, 创建了4个工作线程,调用GetQueuedCompletionStatus时,该调用线程就进入了睡眠状态,假设这个时候,I/O完成队列出现了三项,调用线程的ID就被放入该等待线程队列中。

I/O完成端口内核对象(第3个参数等级线程队列),因此知道哪些线程正在等待处理完成的I/O请求。当端口的I/O完成队列出现一项时,完成端口就唤醒(睡眠状态中变为可调度状态)等待线程队列中的一个线程(前面讲过等待线程队列是后进先出)。所以线程D将得到完成I/O项中的信息:传输的字节数,完成键(单句柄数据结构)和Overlapped结构地址,线程是通过GetQueuedCompletionStatus返回这些信息。

在前面我们指定了并发线程的数目是2,所以I/O完成端口唤醒2个线程,线程D和线程C,另两个继续休眠(线程B,线程A),直到线程D处理完了,发现表项里还有要处理的,就唤醒同一线程继续处理。

线程并发量:

并发量限制了与该完成端口相关联的可运行线程的数目, 它类似阀门的作用。 当与该完成端口相关联的可运行线程的总数目达到了该并发量,系统就会阻塞任何与该完成端口相关联的后续线程的执行, 直到与该完成端口相关联的可运行线程数目下降到小于该并发量为止。所以解释了线程池中的运行线程可能会比设置的并发线程多的原因。

它的作用:

最有效的假想是发生在有完成包在队列中等待,而没有等待被满足,因为此时完成端口达到了其并发量的极限。此时,一个正在运行中的线程调用 GetQueuedCompletionStatus时,它就会立刻从队列中取走该完成包。这样就不存在着环境的切换,因为该处于运行中的线程就会连续不断地从队列中取走完成包,而其他的线程就不能运行了。

注意:如果池中的所有线程都在忙,客户请求就可能拒绝,所以要适当调整这个参数,获得最佳性能。

线程并发:D线程挂起,加入暂停线程,醒来后又加入释放线程队列。

线程的安全退出:

PostQueudCompletionStatus函数,我们可以用它发送一个自定义的包含了OVERLAPPED成员变量的结构地址,里面包含一个状态变量,当状态变量为退出标志时,线程就执行清除动作然后退出。

完成端口使用需要注意的地方:

1.在执行wsasend和wsarecv操作前,请先将overlapped结构体使用memset进行清零。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭