簡介
輸入輸出完成連線埠(Input/Output Completion Port,IOCP), 是支持多個同時發生的異步I/O操作的應用程式編程接口,在Windows NT的3.5版本以後,或AIX5版以後或Solaris第十版以後,開始支持。
IOCP特別適合C/S模式網路伺服器端模型。因為,讓每一個socket有一個執行緒負責同步(阻塞)數據處理,one-thread-per-client的缺點是:一是如果連入的客戶多了,就需要同樣多的執行緒;二是不同的socket的數據處理都要執行緒切換的代價。
原理
通常的辦法是,執行緒池中的工作執行緒的數量與CPU核心數量相同,以此來最小化執行緒切換代價。一個IOCP對象,在作業系統中可關聯著多個Socket和(或)檔案控制端。 IOCP對象內部有一個先進先出(FIFO)佇列,用於存放IOCP所關聯的輸入輸出端的服務請求完成訊息。請求輸入輸出服務的進程不接收IO服務完成通知,而是檢查IOCP的訊息佇列以確定IO請求的狀態。 (執行緒池中的)多個執行緒負責從IOCP訊息佇列中取走完成通知並執行數據處理;如果佇列中沒有訊息,那么執行緒阻塞掛起在該佇列。這些執行緒從而實現了負載均衡。
Windows作業系統
IOCP是唯一一個不需要安全屬性的Windows核心對象。 這是因為IO完成連線埠在設計時就是只在一個進程中使用。
使用CreateIoCompletionPort函式創建一個新的IOCP,或把socket或檔案句柄與一個已存在的IOCP關聯起來。
一個執行緒,第一次調用GetQueuedCompletionStatus函式時,該執行緒就成為關聯了該IOCP的執行緒,直到下述三種情形之一發生:
•該執行緒退出;
•該執行緒調用GetQueuedCompletionStatus函式關聯到其他的IOCP;
•該IOCP被關閉。
即,一個執行緒在任何時刻最多關聯一個IOCP。
執行緒調用GetQueuedCompletionStatus函式等待放入IOCP的I/O 完成包(completion packet)。IOCP擁有一個執行緒池。阻塞在IOCP上的執行緒按照後進先出(LIFO)順序被釋放(這是為了減少執行緒切換的代價);而一個執行緒的完成包按照先進先出(FIFO)順序從IOCP的佇列中取走。IOCP有一個最大允許並發的執行緒數量上限,在CreateIoCompletionPort函式中指定,每次I/O完成包在從佇列取走前檢查關聯於該IOCP且正在並發執行的執行緒數量是否達到該限。因其他原因(如調用SuspendThread函式)而掛起的執行緒不算作正在執行的執行緒。CompletionKey(完成鍵)一般作為“單句柄數據”的結構體(PER_HANDLE_DATA),用來標識是哪個設備的I/O完成操作己經完成。IO重疊結構(Overlapped)一般作為“單IO數據”的結構體(PER_IO_DATA),該結構體的第1個成員為OVERLAPPED結構體,用來標識是設備的具體哪個操作。
執行緒可以用PostQueuedCompletionStatus函式在IOCP上投寄一個完成包。
關閉IOCP之前,必須先關閉關聯在該IOCP之上的所有File Handle或socket。
內部結構
Jeffrey Richter說:“完成連線埠可能是最為複雜的核心對象”。Windows中利用CreateIoCompletionPort命令創建完成連線埠對象時, 作業系統內部為該對象自動創建了5個數據結構,分別是:
•設備列表(Device List): 每當調用CreateIoCompletionPort函式時,作業系統會將該設備句柄添加到設備列表中;每當調用CloseHandle關閉了某個設備句柄時,系統會將該設句柄從設備列表中刪除
•IO完成請求佇列(I/O Completion Queue-FIFO):當I/O請求操作完成時,或者調用了PostQueuedCompeltionStatus函式時,作業系統會將I/O請求完成包添加到I/O完成佇列中。當作業系統從完成連線埠對象的等待執行緒佇列中取出一個工作執行緒時,作業系統會同時從I/O完成佇列中取出一個元素(I/O請求完成包。
•等待執行緒佇列(WaitingThread List-LIFO):當執行緒中調用GetQueuedCompletionStatus函式時,作業系統會將該執行緒壓入到等待執行緒佇列中。為了減少執行緒切換,該佇列是LIFO。當I/O完成佇列非空,且工作執行緒並未超出總的並發數時,系統從等待執行緒佇列中取出執行緒,該執行緒從自身代碼的GetQueuedCompletoinStatus函式調用處返回並繼續運行。
•釋放執行緒佇列(Released Thread List):當作業系統從等待執行緒佇列中激活了一個工作執行緒時,或者掛起的執行緒重新被激活時,該執行緒被壓入釋放執行緒佇列中,也即這個佇列的執行緒處於運行狀態。這個佇列中的執行緒有兩個出佇列的機會:一是當執行緒重新調用GetQueuedCompeltionStatus函式時,執行緒被添加到等待執行緒佇列中;二是當執行緒調用其他函式使得執行緒掛起時,該執行緒被添加到“暫停執行緒佇列”中。
•暫停執行緒佇列(Paused Thread List):釋放執行緒佇列中的執行緒被掛起的時候,執行緒被壓入到“暫停執行緒佇列”中;當掛起的執行緒重新被喚醒時,從“暫停執行緒佇列”中取出放入到釋放執行緒佇列。