1. 程式人生 > >muduo網路庫——詳解muduo多執行緒模型

muduo網路庫——詳解muduo多執行緒模型

6.3

    非阻塞網路程式設計應該用邊沿觸發(ET)還是電平觸發(LT)?如果是電平觸發,那麼什麼時候關注POLLOUT事件?會不會造成busy-loop?如果是邊沿觸發,如果和防止漏讀造成的飢餓?

epoll一定比poll快麼?

6.4 

      在finger的測試程式中,沒有設定onConnection這個函式,就可以連線?

6.6 詳解muduo多執行緒模型

      方案0: accept+read/write  一次服務一個客戶

      這個不是併發伺服器。

     方案1: accept+fork()        process-per-connection

      這個是傳統的UNIX併發網路程式設計方案,也叫process-per-connextion。這種方案適合併發連線數不大的情況。“計算響應的工作量大於fork()的開銷" 每一個連線開啟一個程序來處理

      方案2:accept+thread      thread-per-connection

        同樣適合長連線,每一個連線開啟一個執行緒,相對於方案1來說,開銷是小了一點,但是對於多個併發連線,也是不合適的,執行緒數目太多,執行緒上下文切換也是需要很多時間的!

        方案3:perfork() 在UNP中存在

        方案4

:pre threaded 在UNP中存在

       方案3和方案4是分別對方案1和方案2的改進

        到目前為止,上述的方案都是阻塞式網路程式設計。比較好的方法就是IO multiplexing,讓一個程式流程(thread of control)控制多個連線。IO複用其實複用的不是IO連線,而是複用執行緒。

       Doug Schmidt指出,趨勢網路程式設計中有很多是事務性的工作,可以提取為公用的框架或庫,而使用者只需要填上關鍵的業務程式碼,並將回撥註冊到框架中,就可以實現完整的網路服務,這也是Reactor模式的主要思想。

         但執行緒Reactor的程式執行順序是這樣的,在沒有事件的時候,執行緒等待在seleect/poll/epoll_wait等函式上。事件到達後由網路庫處理IO,再把訊息通知(回撥)客戶端程式碼。Reactor事件迴圈所在的執行緒通常叫IO執行緒。通常由網路庫負責讀寫socket,使用者程式碼負責解碼、計算、編碼。

         注意由於只有一個執行緒,因此事件是順序處理的,一個執行緒同時只能做一件事情。事件的優先順序不能保證,因為從poll返回之後到下一次呼叫poll進入等待之前這段時間內,執行緒不會被其他連結上的資料或事件搶佔。如果我們想延遲計算(把compute()推遲100ms),那麼也不能用sleep()之類的阻塞呼叫,而應該註冊超時回撥,以避免阻塞當前IO執行緒。(理解的整個過程是這樣的:poll返回以後,會對相應的套接字上的事件做相應處理,把得到的資料程序compute處理,然後再回應對端,如果在進行處理的過程中有別的套接字上存在時間,那麼這個事件不會得到立即相應,而是等到迴應結束再次進入poll才能得到迴應。後半句的意思,如果在poll返回之後準備處理事件的時候,使用者想讓compute延遲100ms,那麼不能使用sleep()這種阻塞式系統呼叫,應該註冊延遲迴調,這樣就不會阻塞當前執行緒。這樣的話,註冊延遲以後,就可以繼續做下面的操作,而不是等待100ms的事件耗盡,等到100ms時間到,計算操作就會被啟用。)

        方案5:poll(reactor)  單執行緒rector

        這個方案就是上面解釋。這種方案的優點是由網路庫搞定資料收發,程式之關心業務邏輯;缺點:適合IO密集的應用,不太適合CPU密集的應用,因為較難發揮多核的威力。

        在使用非阻塞IO+事件驅動方式程式設計的時候,一定要注意避免在事件回撥中執行耗時的操作,包括阻塞IO等,否則會影響程式的相應。

        方案6:rector+thread-per-task   thread-per-request(每一個請求一個執行緒)

        這是一個過渡方案,需要處理資料請求進行處理時,不在Reactor執行緒計算,而是建立一個新執行緒計算,可以充分利用多核CPU。這是非常初級的多執行緒應用,因為它為每個請求(而不是每個連線)建立一個新執行緒。這個開銷可以用執行緒池來避免,就是方案8.這個方案的缺點就是out-of-order,即同時建立多個執行緒去計算同一個連線上收到的多個請求,那麼算出結果的次序是不確定的。

        方案7:reactor+worker thread     worker-thread-per-connection

        為了讓返回結果的順序確定,我們可以為每個連線建立一個計算執行緒,每個連線上的請求固定發給同一個執行緒去算。先到先得。這也是一個過渡方案,因為併發連線數受限於執行緒數目,這個方案或許不如直接使用阻塞IO的thread-per-connection方案2

       方案8: reactor+thread poll    主執行緒IO,工作執行緒計算

        為了彌補方案6中為每個請求建立執行緒的缺陷,可以使用固定大小執行緒池,全部的IO工作都在一個Reactor執行緒完成,而計算任務交給thread poll。如果計算任務彼此獨立,而且IO的壓力不大,那麼這種方案非常適用。

        在這個方案中,thread poll只相當於處理操作。

        這個方案和方案5的單執行緒Reactorxiangbi 變化不大,只是把計算和發回響應的部分做成一個函式,然後交給ThreadPool去做。

        執行緒池的另外一個作用是執行阻塞操作。比如有的資料庫的客戶端指提供同步訪問,那麼可以把資料庫查詢放到執行緒池中,可以避免阻塞IO執行緒,不會影響其他客戶連線(在方案5中是不可以呼叫阻塞函式的)

        缺點是:如果IO的壓力比較大,一個Reactor處理不過來,可以嘗試方案9,它採用多個Reactor來分擔負載。

        方案9:reactors in threads  one loop per thread(muduo)

        方案的特點是one loop per thread,有一個main reactor負載accept連線,然後把連線掛載某個sub reactor(採用round-robin的方式來選擇sub reactor),這樣改連線的所用操作都在那個sub reactor所處的執行緒中完成。多個連線可能被分派到多個執行緒中,以充分利用CPU.

        Reactor poll的大小是固定的,根據CPU的數目確定。

        一個Base IO thread負責accept新的連線,接收到新的連線以後,使用輪詢的方式在reactor pool中找到合適的sub reactor將這個連線掛載到上去,一個這個連線上的所有任務都在這個sub reactor上完成。

        方案10:reactors in processes   one loop per process(nginx)

        如果連線之間無互動,這種方案也是很好的選擇。工作程序之間相互獨立。

        方案11: reactor+thread poll

        方案8和方案9的混合,即使用多個Reactor來處理IO,又使用執行緒池來處理計算。這種方案適合既有突發IO(利用多執行緒處理多個連線上的IO),又有突發計算的應用(利用執行緒池把一個連線上的計算任務分配給多個執行緒去做)

        在方案9中,新連線被掛載到sub reactor上,在這個sub reactor上進行新連線上資料的計算和迴應。