本文僅是對(duì)Windows 在線文檔(部分)的翻譯
I/O完成端口為多處理器系統(tǒng)上的異步I/O請(qǐng)求提供了一個(gè)高效的線程模式提针。當(dāng)進(jìn)程創(chuàng)建一個(gè)I/O完成端口時(shí)海雪,系統(tǒng)會(huì)創(chuàng)建相關(guān)的一系列隊(duì)列。結(jié)合一個(gè)預(yù)先初始化的線程池敢伸,進(jìn)程通過(guò)使用完成端口可以更快扯饶,更高效地處理大量并發(fā)的異步I/O請(qǐng)求恒削。
1 I/O完成端口工作流程
CreateIoCompletionPort
函數(shù)用于創(chuàng)建I/O完成端口對(duì)象并將一個(gè)或多個(gè)文件句柄關(guān)聯(lián)到該端口上池颈。當(dāng)這些文件句柄上的異步I/O操作完成時(shí),會(huì)以先入先出的順序向該完成端口的I/O完成隊(duì)列壓入一個(gè)完成封包钓丰。這個(gè)機(jī)制的強(qiáng)大之處在于將多個(gè)文件句柄的同步點(diǎn)整合到一個(gè)對(duì)象上躯砰。當(dāng)然,這個(gè)機(jī)制也有其它用武之地携丁。注意琢歇,雖然完成封包以先入先出的順序入隊(duì),但可能以其它順序出隊(duì)梦鉴。
術(shù)語(yǔ)file handle指一個(gè)抽象的重疊I/O設(shè)備李茫,而不僅僅是磁盤上的文件。例如肥橙,可能是一個(gè)網(wǎng)絡(luò)設(shè)備魄宏,TCP套接字,命名管道存筏,或郵件槽宠互。其可以是任何支持重疊I/O的系統(tǒng)對(duì)象。
當(dāng)某文件句柄和完成端口關(guān)聯(lián)后椭坚,除非該文件句柄上的完成封包從完成端口移除或原始操作同步地返回了錯(cuò)誤予跌,否則文件句柄的狀態(tài)不會(huì)更新。線程(由主線程創(chuàng)建的其它線程或主線程自己)使用GetQueuedCompletionStatus
函數(shù)等待壓入到完成端口隊(duì)列的完成封包善茎,而不是直接等待異步I/O完成券册。在完成端口上阻塞的線程將以后入先出的順序喚醒,而下一個(gè)完成封包將會(huì)以先入先出的順序從完成端口的I/O完成隊(duì)列中拉取。也就是說(shuō)烁焙,當(dāng)將完成封包分配給線程處理時(shí)略吨,系統(tǒng)會(huì)喚醒最近與該完成端口關(guān)聯(lián)的線程。
在指定的完成端口上不限定調(diào)用GetQueuedCompletionStatus
的線程數(shù)考阱。當(dāng)線程第一次調(diào)用GetQueuedCompletionStatus
時(shí)翠忠,他將和指定的完成端口關(guān)聯(lián)(等待線程隊(duì)列),一直到以下情況發(fā)生:
- 線程退出
- 后續(xù)調(diào)用
GetQueuedCompletionStatus
時(shí)指定另外的完成端口 - 關(guān)閉了完成端口
換句話說(shuō)乞榨,一個(gè)線程在同一時(shí)刻最多只能關(guān)聯(lián)一個(gè)完成端口秽之。
當(dāng)有完成封包到達(dá)時(shí),系統(tǒng)首先檢查當(dāng)前有多少與完成端口關(guān)聯(lián)的線程正在運(yùn)行吃既。如果正在運(yùn)行的線程數(shù)小于指定的最大并發(fā)數(shù)考榨,會(huì)喚醒其中一個(gè)線程(最近的那個(gè))以處理完成封包。當(dāng)運(yùn)行的線程完成其處理時(shí)鹦倚,通常會(huì)再次調(diào)用GetQueuedCompletionStatus
河质,這時(shí)候,如果完成端口的I/O完成隊(duì)列不為空震叙,它將繼續(xù)處理下一個(gè)完成封包掀鹅,否則線程阻塞,等待完成封包媒楼。
線程可以調(diào)用PostQueuedCompltionStatus
函數(shù)向完成端口的I/O完成隊(duì)列添加特殊的完成封包乐尊。這樣完成端口還可被用來(lái)從進(jìn)程的其它線程接收自定義消息。這通常用于告知工作線程某些外部事件划址,如應(yīng)用程序即將終止運(yùn)行扔嵌。
I/O完成端口句柄以及與之關(guān)聯(lián)的文件句柄一起稱之為對(duì)完成端口的引用。只有當(dāng)沒(méi)有引用時(shí)夺颤,完成端口對(duì)象才能被釋放痢缎。因此,為了釋放完成端口對(duì)象及相關(guān)資源世澜,所有這些句柄必須被正確地關(guān)閉独旷。在所有這些條件滿足后,應(yīng)用程序必須調(diào)用CloseHandle
關(guān)閉完成端口句柄宜狐。
I/O完成端口和創(chuàng)建它的進(jìn)程關(guān)聯(lián)势告,無(wú)法在進(jìn)程間共享,但是可以被進(jìn)程內(nèi)的線程共享抚恒。
2 線程和并發(fā)
完成端口最重要的屬性是最大并發(fā)數(shù)咱台。完成端口的最大并發(fā)數(shù)在其通過(guò)CreateIoCompletionPort
創(chuàng)建時(shí)由NumberOfConcurrentThreads參數(shù)指定。這個(gè)參數(shù)限制與完成端口關(guān)聯(lián)的線程的可運(yùn)行數(shù)俭驮。如果同完成端口關(guān)聯(lián)的正在運(yùn)行的線程數(shù)已達(dá)到指定的最大并發(fā)值回溺,系統(tǒng)將阻止其它關(guān)聯(lián)線程被喚醒春贸,直到正在運(yùn)行的線程數(shù)小于最大并發(fā)數(shù)。
最高效的情形是遗遵,當(dāng)隊(duì)列中有完成封包等待時(shí)萍恕,由于完成端口上正在運(yùn)行的線程數(shù)已達(dá)到其最大并發(fā)數(shù)而不會(huì)喚醒任何其它線程。在這種情況下车要,如果完成端口的隊(duì)列中總是有正在等待的完成封包允粤,當(dāng)正在運(yùn)行的線程處理完上一個(gè)封包,然后調(diào)用GetQueuedCompletionStatus
時(shí)翼岁,其不會(huì)阻塞而是立即獲得下一個(gè)完成封包并處理类垫。這時(shí)沒(méi)有線程上下文切換,因?yàn)檫\(yùn)行中的線程是連續(xù)地獲得完成封包的琅坡,同時(shí)其它線程仍然不能運(yùn)行悉患。
在上面的例子中,額外的線程似乎沒(méi)什么用榆俺,因?yàn)樗鼈儚膩?lái)不運(yùn)行售躁。但是上面的情況是假設(shè)運(yùn)行線程從來(lái)不會(huì)因?yàn)槠渌鼨C(jī)制而進(jìn)入等待狀態(tài)。
顯然茴晋,合適的最大并發(fā)數(shù)是機(jī)器的CPU數(shù)陪捷。如果線程處理的事務(wù)需要長(zhǎng)時(shí)間運(yùn)算,更大的并發(fā)數(shù)將允許更多線程得以運(yùn)行晃跺。有些完成封包可能需要較長(zhǎng)的時(shí)間進(jìn)行處理揩局,但多數(shù)完成封包的處理時(shí)間是差不多的∠苹ⅲ可以通過(guò)數(shù)值試驗(yàn)獲取最佳的最大并發(fā)數(shù)。
如果與完成端口關(guān)聯(lián)的正在運(yùn)行的線程因?yàn)槠渌蜻M(jìn)入等待狀態(tài)付枫,例如烹玉,調(diào)用了SuspendThread
函數(shù),系統(tǒng)會(huì)允許因調(diào)用GetQueuedCompletionStatus
而等待運(yùn)行的線程處理完成封包阐滩。當(dāng)之前進(jìn)入等待的線程又開(kāi)始運(yùn)行時(shí)二打,可能有一個(gè)短暫的時(shí)間實(shí)際運(yùn)行的線程數(shù)大于最大并發(fā)數(shù)。但是系統(tǒng)會(huì)通過(guò)禁止喚醒其它等待線程而快速減小這個(gè)實(shí)際并發(fā)數(shù)掂榔。這就是為什么應(yīng)用程序要將線程池線程數(shù)設(shè)置的比完成端口最大并發(fā)數(shù)大的原因继效。
3 支持函數(shù)
下面的函數(shù)可用于開(kāi)始使用I/O完成端口的I/O操作。必須向函數(shù)傳遞OVERLAPPED
結(jié)構(gòu)體實(shí)例且在此之前必須將相關(guān)的文件句柄和完成端口關(guān)聯(lián):
- ConnectNamedPipe
- DeviceIoControl
- LockFileEx
- ReadDirectoryChangesW
- ReadFile
- TransactNamedPipe
- WaitCommEvent
- WriteFile
- WSASendMsg
- WSASendTo
- WSARecvFrom
- WSARecvMsg
- WSARecv
4 APIs
CreateIoCompletionPort
創(chuàng)建一個(gè)I/O完成端口并將其和指定的文件句柄關(guān)聯(lián)装获,或僅僅是創(chuàng)建一個(gè)完成端口瑞信。
在I/O完成端口上關(guān)聯(lián)一個(gè)打開(kāi)的文件句柄將允許進(jìn)程接收該文件句柄上的異步I/O操作的完成通知。
這里文件句柄是一個(gè)系統(tǒng)抽象的名詞穴豫,其代表一個(gè)重疊I/O端而不是磁盤上的一個(gè)文件凡简。任何支持重疊I/O的系統(tǒng)對(duì)象逼友,如網(wǎng)絡(luò)端點(diǎn)、TCP socket秤涩、命名管道或mail slots都可當(dāng)做文件句柄帜乞。
Syntax
HANDLE WINAPI CreateIoCompletionPort(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE ExistingCompletionPort,
_In_ ULONG_PTR CompletionKey,
_In_ DWORD NumberOfConcurrentThreads
);
Parameters
FileHandle
一個(gè)打開(kāi)的文件句柄或者是INVALID_HANDLE_VALUE
。
該句柄必須是支持重疊I/O的對(duì)象筐眷。
如果指定了文件句柄黎烈,其必須以重疊I/O模式打開(kāi)。例如匀谣,必須以FILE_FLAG_OVERLAPPED
標(biāo)識(shí)調(diào)用CreateFile
函數(shù)以獲得一個(gè)文件句柄怨喘。
如果指定了INVALID_HANDLE_VALUE
,函數(shù)將只是創(chuàng)建一個(gè)新的完成端口振定,這種情況下必怜,ExistingCompletionPort必須是NULL
且CompletionKey會(huì)被忽略。
ExistingCompletionPort
一個(gè)已存在的I/O完成端口句柄或者是NULL后频。
如果指定了一個(gè)已存在的完成端口梳庆,函數(shù)會(huì)將其和參數(shù)FileHandle指定的文件句柄關(guān)聯(lián)。如果函數(shù)執(zhí)行成功返回該完成端口卑惜。
如果該參數(shù)為NULL
膏执,函數(shù)將創(chuàng)建一個(gè)新的I/O完成端口。如果指定了有效的文件句柄(FileHandle)露久,則新創(chuàng)建的完成端口會(huì)和其關(guān)聯(lián)更米,否則只是新建一個(gè)完成端口。函數(shù)返回該新建的完成端口毫痕。
CompletionKey
包含于每個(gè)I/O完成封包中的用戶自定義的pre-handle征峦。
NumberOfConcurrentThreads
對(duì)每個(gè)I/O完成端口,操作系統(tǒng)允許的最大線程數(shù)以同時(shí)處理I/O完成封包消请。如果ExistingCompletionPort參數(shù)不為NULL栏笆,該參數(shù)會(huì)被忽略。
如果該參數(shù)為0臊泰,系統(tǒng)將允許和系統(tǒng)處理器個(gè)數(shù)一樣的線程數(shù)同時(shí)運(yùn)行蛉加。
Return value
函數(shù)執(zhí)行成功必然返回一個(gè)I/O完成端口。
- 如果ExistingCompletionPort為
NULL
缸逃,返回一個(gè)新的完成端口针饥。 - 如果ExistingCompletionPort為一個(gè)有效的完成端口,則返回這個(gè)完成端口需频。
- 如果FileHandle是一個(gè)有效的文件句柄丁眼,該文件句柄將會(huì)和返回的完成端口關(guān)聯(lián)。
- 如果函數(shù)失敗贺辰,返回
NULL
户盯,可通過(guò)GetLastError
函數(shù)獲取擴(kuò)展錯(cuò)誤碼嵌施。
Remarks
I/O完成端口和創(chuàng)建它的進(jìn)程關(guān)聯(lián),其它進(jìn)程不可見(jiàn)莽鸭,但是同一進(jìn)程內(nèi)的線程之間可共享吗伤。
文件句柄僅可和一個(gè)完成端口關(guān)聯(lián),一旦完成關(guān)聯(lián)硫眨,直到該文件句柄關(guān)閉該關(guān)聯(lián)將一直維持足淆。
可多次調(diào)用CreateIoCompletionPort
函數(shù)將多個(gè)文件句柄關(guān)聯(lián)到一個(gè)完成端口上。
使用CompletionKey參數(shù)幫助應(yīng)用程序跟蹤究竟是哪個(gè)重疊IO已經(jīng)完成礁阁。這個(gè)參數(shù)并沒(méi)有參與CreateIoCompletionPort
的內(nèi)部功能控制巧号,其只是綁定到文件句柄上。對(duì)于每個(gè)文件句柄來(lái)說(shuō)這個(gè)CompletionKey必須是唯一的姥闭,且其在整個(gè)內(nèi)部處理期間都會(huì)伴隨文件句柄丹鸿。當(dāng)完成封包到來(lái)時(shí),可通過(guò)GetQueuedCompletionStatus
函數(shù)獲取這個(gè)CompletionKey棚品。CompletionKey也可用于PostQueuedCompletionStatus
函數(shù)以入隊(duì)用戶自定義完成封包靠欢。
當(dāng)文件句柄和某IO完成端口關(guān)聯(lián)后,其不可再用于ReadFileEx
函數(shù)和WriteFileEx
函數(shù)铜跑,因?yàn)檫@些函數(shù)有它們自己的異步IO機(jī)制门怪。
最好不要以句柄繼承或調(diào)用DuplicateHandle
函數(shù)的方式共享已經(jīng)與IO完成端口關(guān)聯(lián)的文件句柄。使用這種多重句柄執(zhí)行操作時(shí)也會(huì)產(chǎn)生完成通知锅纺,還是小心為妙掷空。
IO完成端口句柄和與其關(guān)聯(lián)的文件句柄我們稱之為IO完成端口引用(reference to the I/O completion port),當(dāng)沒(méi)有引用時(shí)必須釋放IO完成端口囤锉。所有這些句柄必須正確地關(guān)閉以釋放IO完成端口及其關(guān)聯(lián)的系統(tǒng)資源坦弟。當(dāng)滿足上述條件時(shí),可調(diào)用CloseHandle
關(guān)閉IO完成端口嚼锄。
GetQueuedCompletionStatus
嘗試從指定的IO完成端口上出隊(duì)一個(gè)IO完成封包减拭。如果隊(duì)列中沒(méi)有完成封包,函數(shù)將等待完成端口上某個(gè)未決IO操作完成区丑。
如果需要一次出隊(duì)多個(gè)IO完成封包,使用GetQueuedCompletionStatusEx
函數(shù)修陡。
Syntax
BOOL WINAPI GetQueuedCompletionStatus(
_In_ HANDLE CompletionPort,
_Out_ LPDWORD lpNumberOfBytes,
_Out_ PULONG_PTR lpCompletionKey,
_Out_ LPOVERLAPPED *lpOverlapped,
_In_ DWORD dwMilliseconds
);
Parameters
CompletionPort
lpNumberOfBytes
用于保存已完成的IO操作在其執(zhí)行期間傳輸?shù)淖止?jié)總數(shù)沧侥。
lpCompletonKey
當(dāng)某文件句柄上的IO操作完成時(shí),用于保存與該文件句柄關(guān)聯(lián)的completion key的值魄鸦。
lpOverlapped
用于保存某OVERLAPPED結(jié)構(gòu)體的地址宴杀,其在IO操作開(kāi)始時(shí)被指定。
即使已將文件句柄和完成端口關(guān)聯(lián)且指定了有效的OVERLAPPED
結(jié)構(gòu)拾因,應(yīng)用程序也可以阻止完成通知旺罢。為此旷余,必須在OVERLAPPED
結(jié)構(gòu)的hEvent成員中保存一個(gè)有效的事件句柄并設(shè)置其最低位:
Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
Overlapped.hEvent = (HANDLE) ((DWORD_PTR) Overlapped.hEvent | 1);
...
另外扁达,在關(guān)閉這個(gè)事件句柄時(shí)不要忘了將最低位清掉:
CloseHandle((HANDLE) ((DWORD_PTR) Overlapped.hEvent & ~1));
dwMilliseconds
調(diào)用者愿意在完成端口上等待完成封包的毫秒數(shù)正卧。如果在指定的時(shí)間內(nèi)沒(méi)有完成封包出現(xiàn)跪解,函數(shù)將返回FALSE
同時(shí)設(shè)置lpOverlapped為NULL
。
如果為INFINITE
惶看,函數(shù)將阻塞執(zhí)行莹桅。
Return value
這個(gè)函數(shù)將線程和指定的完成端口關(guān)聯(lián)煤禽。一個(gè)線程只能和一個(gè)完成端口關(guān)聯(lián)选脊。
當(dāng)調(diào)用GetQueuedCompletionStatus
時(shí)如果與之關(guān)聯(lián)的完成端口已經(jīng)關(guān)閉钝的,函數(shù)將失敗并返回FALSE
棍弄,且lpOverlapped為NULL
蛮原,同時(shí)GetLastError
返回ERROR_ABANDONED_WAIT_0
擴(kuò)展錯(cuò)誤碼蹦漠。
如果GetQueuedCompletionStatus
函數(shù)執(zhí)行成功研铆,將從完成端口上出隊(duì)一個(gè)完成封包(對(duì)應(yīng)一個(gè)成功的IO操作)棵红,并將其信息存儲(chǔ)到lpNumberOfBytes, lpCompletionKey和lpOverlapped參數(shù)中;如果執(zhí)行失敗咧栗,這些參數(shù)可能包含以下特定的值:
- 如果lpOverlapped為
NULL
逆甜,函數(shù)沒(méi)有從完成端口上出隊(duì)一個(gè)完成封包,這種情況下致板,函數(shù)不會(huì)在lpNumberOfBytes, lpCompletionKey中存儲(chǔ)信息忆绰,它們的值是不確定的。 - 如果lpOverlapped不為
NULL
可岂,函數(shù)從完成端口上出隊(duì)一個(gè)完成封包,但該完成封包對(duì)應(yīng)一個(gè)失敗的IO操作翰灾,函數(shù)會(huì)將該失敗的IO操作的信息存儲(chǔ)到lpNumberOfBytes, lpCompletionKey和lpOverlapped中缕粹,可通過(guò)GetLastError
獲取擴(kuò)展錯(cuò)誤碼稚茅。
PostQueuedCompletionStatus
向完成端口提交一個(gè)IO完成封包。
Syntax
BOOL WINAPI PostQueuedCompletionStatus(
_In_ HANDLE CompletionPort,
_In_ DWORD dwNubmerOfBytesTransferred,
_In_ ULONG_PTR dwCompletionKey,
_In_opt_ LPOVERLAPPED lpOverlapped
);
Parameters
CompletionPort
dwNumberOfBytesTransferred
dwCompletonKey
lpOverlapped
上述3個(gè)參數(shù)指定當(dāng)調(diào)用GetQueuedCompletionStatus
時(shí)對(duì)應(yīng)參數(shù)帶回的值平斩。
Return value
函數(shù)執(zhí)行成功返回非零值亚享,否則返回0.
Remarks
提交的完成封包完全滿足GetQueuedCompletonStatus
的要求。系統(tǒng)不會(huì)使用這個(gè)封包也不會(huì)驗(yàn)證其正確性绘面。尤其是欺税,lpOverlapped參數(shù)不必是指向OVERLAPPED
結(jié)構(gòu)體的指針。