当前位置:首页 > 芯闻号 > 充电吧
[导读]完成端口(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进行清零。

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

特朗普集团近日取消了其新推出的T1智能手机“将在美国制造”的宣传标语,此举源于外界对这款手机能否以当前定价在美国本土生产的质疑。

关键字: 特朗普 苹果 AI

美国总统特朗普在公开场合表示,他已要求苹果公司CEO蒂姆·库克停止在印度建厂,矛头直指该公司生产多元化的计划。

关键字: 特朗普 苹果 AI

4月10日消息,据媒体报道,美国总统特朗普宣布,美国对部分贸易伙伴暂停90天执行新关税政策,同时对中国的关税提高到125%,该消息公布后苹果股价飙升了15%。这次反弹使苹果市值增加了4000多亿美元,目前苹果市值接近3万...

关键字: 特朗普 AI 人工智能 特斯拉

3月25日消息,据报道,当地时间3月20日,美国总统特朗普在社交媒体平台“真实社交”上发文写道:“那些被抓到破坏特斯拉的人,将有很大可能被判入狱长达20年,这包括资助(破坏特斯拉汽车)者,我们正在寻找你。”

关键字: 特朗普 AI 人工智能 特斯拉

1月22日消息,刚刚,新任美国总统特朗普放出重磅消息,将全力支持美国AI发展。

关键字: 特朗普 AI 人工智能

特朗普先生有两件事一定会载入史册,一个是筑墙,一个是挖坑。在美墨边境筑墙的口号确保边境安全,降低因非法移民引起的犯罪率过高问题;在中美科技产业之间挖坑的口号也是安全,美国企业不得使用对美国国家安全构成威胁的电信设备,总统...

关键字: 特朗普 孤立主义 科技产业

据路透社1月17日消息显示,知情人士透露,特朗普已通知英特尔、铠侠在内的几家华为供应商,将要撤销其对华为的出货的部分许可证,同时将拒绝其他数十个向华为供货的申请。据透露,共有4家公司的8份许可被撤销。另外,相关公司收到撤...

关键字: 华为 芯片 特朗普

曾在2018年时被美国总统特朗普称作“世界第八奇迹”的富士康集团在美国威斯康星州投资建设的LCD显示屏工厂项目,如今却因为富士康将项目大幅缩水并拒绝签订新的合同而陷入了僵局。这也导致富士康无法从当地政府那里获得约40亿美...

关键字: 特朗普 富士康

今年5月,因自己发布的推文被贴上“无确凿依据”标签而与推特发生激烈争执后,美国总统特朗普签署了一项行政令,下令要求重审《通信规范法》第230条。

关键字: 谷歌 facebook 特朗普

众所周知,寄往白宫的所有邮件在到达白宫之前都会在他地进行分类和筛选。9月19日,根据美国相关执法官员的通报,本周早些时候,执法人员截获了一个寄给特朗普总统的包裹,该包裹内包含蓖麻毒蛋白。

关键字: 美国 白宫 特朗普
关闭