完成端口(Q&A)

  1. 程序設計上,線程不怕多,怕切換的頻繁,即使有多於cpu個數的線程,例如使用完成端口的時候,總會有個主線程,但這個主線程在配置完完成端口後就徹底的休息了(可以是join或信號量方式),cpu一直工作在完成端口的工作線程上,也不會頻繁切換。

  2. 爲什麼完成端口中工作線程的數目爲cpu*2?設計的目的是爲了讓cpu滿負荷工作。會不會造成額外的線程切換?會,但正常情況下不會,這裏異常就是指處理線程進入阻塞狀態(Sleep()或者WaitForSingleObject()),這個時候cpu會切換到其他的線程。正常情況下,如果線程沒有進入阻塞狀態,在滿負荷的工作,那麼cpu會等待這個線程重回隊列然後從隊列中取出這個線程再次工作(後進先出,爲了減少切換),所以正常情況下,雖然有cpu*2個線程,但是工作的線程也只有cpu數目個,不會有線程頻繁切換,也可以把這些長期睡眠的線程換出內存。

  3. ACE中的前攝器使用與完成端口有些類似,WSARecv對應ACE中的ACE_Asynch_Read_Stream,調用ACE_Asynch_Read_Stream的read請求最終在windows下調用的爲WSARecv方法,投遞了一個讀的請求。所以使用起來也差不多,在連接建立後立刻投遞一個,然後其他的投遞都是在工作線程中處理完之後。

  4. 完成端口在win8中有個bug,如果主線程在最後等待退出的部分用的是一個while(){getline()}想處理一些調試命令的話,如果使用AcceptEx實現完成端口,那麼線程中GetQueuedCompletionStatus() 是不會讀到Accept事件的,除非在cmd上輸入個回車,讓那個while循環執行一次。

  5. 對於完成端口的使用可以參考http://qiusuoge.com/12451.html,寫的非常詳細可惜沒有找到對應的完整代碼。說是最複雜的網絡模型,但是看下來也沒有什麼新的概念與特殊的設計方法。之所以複雜可能與設計的比較靈活有關係。
    使用過程中涉及到幾個對象線程、socket連接(文件句柄)、完成端口,然後把他們都創建好之後在關聯起來就可以了(有的是創建的時候關聯),設計耦合度比較松。啓動後,投遞一個請求(Read或者Accept(使用AcceptEx的時候有這個請求)),socket監聽消息,如果收到消息通過完成端口會產生一個事件(Acept或者Read),然後通知調用GetQueuedCompletionStatus()並阻塞上面的線程,然後線程中處理,處理完後繼續投遞請求。如此循環。工作方式有點類似使用conditionWait的信號量+消息隊列(其實這個鎖與信號量結合理解也比較麻煩)。

其他的問題,直接引用別的文章中的文字:

1.至於爲什麼叫Overlapped?Jeffrey Richter的解釋是因爲“執行I/O請求的時間與線程執行其他任務的時間是重疊(overlapped)的
對於完成端口這個概念,我一直不知道爲什麼它的名字是叫“完成端口”,我個人的感覺應該叫它“完成隊列”似乎更合適一些,總之這個“端口”和我們平常所說的用於網絡通信的“端口”完全不是一個東西,我們不要混淆了。

2.而AcceptEx比Accept又強大在哪裏呢?是有三點:
1)這個好處是最關鍵的,是因爲AcceptEx是在客戶端連入之前,就把客戶端的Socket建立好了,也就是說,AcceptEx是先建立的Socket,然後才發出的AcceptEx調用,也就是說,在進行客戶端的通信之前,無論是否有客戶端連入,Socket都是提前建立好了;而不需要像accept是在客戶端連入了之後,再現場去花費時間建立Socket。如果各位不清楚是如何實現的,請看後面的實現部分。

2)相比accept只能阻塞方式建立一個連入的入口,對於大量的併發客戶端來講,入口實在是有點擠;而AcceptEx可以同時在完成端口上投遞多個請求,這樣有客戶端連入的時候,就非常優雅而且從容不迫的邊喝茶邊處理連入請求了。

3)AcceptEx還有一個非常體貼的優點,就是在投遞AcceptEx的時候,我們還可以順便在AcceptEx的同時,收取客戶端發來的第一組數據,這個是同時進行的,也就是說,在我們收到AcceptEx完成的通知的時候,我們就已經把這第一組數據接完畢了;但是這也意味着,如果客戶端只是連入但是不發送數據的話,我們就不會收到這個AcceptEx完成的通知……這個我們在後面的實現部分,也可以詳細看到。

3.如果各位需要使用完成端口來傳送文件的話,這裏有個非常需要注意的地方。因爲發送文件的做法,按照正常人的思路來講,都會是先打開一個文件,然後不斷的循環調用ReadFile()讀取一塊之後,然後再調用WSASend ()去發發送。

但是我們知道,ReadFile()的時候,是需要操作系統通過磁盤的驅動程序,到實際的物理硬盤上去讀取文件的,這就會使得操作系統從用戶態轉換到內核態去調用驅動程序,然後再把讀取的結果返回至用戶態;同樣的道理,WSARecv()也會涉及到從用戶態到內核態切換的問題 — 這樣就使得我們不得不頻繁的在用戶態到內核態之間轉換,效率低下……

而一個非常好的解決方案是使用微軟提供的擴展函數TransmitFile()來傳輸文件,因爲只需要傳遞給TransmitFile()一個文件的句柄和需要傳輸的字節數,程序就會整個切換至內核態,無論是讀取數據還是發送文件,都是直接在內核態中執行的,直到文件傳輸完畢纔會返回至用戶態給主進程發送通知。這樣效率就高多了。

參考:
http://www.cnblogs.com/pen-ink/articles/1834088.html
http://blog.csdn.net/ithzhang/article/details/8508161
http://qiusuoge.com/12451.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章