輸入輸出完成端口(Input/Output Completion Port,IOCP), 是支持多個同時發生的異步I/O操作的應用程式編程接口,在Windows NT的3.5版本以後[1],或AIX 5版以後[2]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英語Jeffrey Richter說:「完成端口可能是最為複雜的內核對象」。[3] 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完成隊列非空,且工作線程並未超出總的並發數時,系統從等待線程隊列中取出線程,該線程從自身代碼的GetQueuedCompletionStatus函數調用處返回並繼續運行。
  • 釋放線程隊列(Released Thread List):當作業系統從等待線程隊列中激活了一個工作線程時,或者掛起的線程重新被激活時,該線程被壓入釋放線程隊列中,也即這個隊列的線程處於運行狀態。這個隊列中的線程有兩個出隊列的機會:一是當線程重新調用GetQueuedCompletionStatus函數時,線程被添加到等待線程隊列中;二是當線程調用其他函數使得線程掛起時,該線程被添加到「暫停線程隊列」中。
  • 暫停線程隊列(Paused Thread List):釋放線程隊列中的線程被掛起的時候,線程被壓入到「暫停線程隊列」中;當掛起的線程重新被喚醒時,從「暫停線程隊列」中取出放入到釋放線程隊列。

參考資料

編輯
  1. ^ Windows I/O Completion Ports. [2014-04-11]. (原始內容存檔於2014-03-20). 
  2. ^ Configuring IOCP on AIX 5 and 6. [2014-04-11]. (原始內容存檔於2013-06-30). 
  3. ^ 《Windows via C/C++》 Fifth Edition (December 2007) (Jeffrey Richter and Christophe Nasarre) Page292.

外部連結

編輯