服務器兩種高效的併發模式

1、併發編程與併發模式react

併發編程主要是爲了讓程序同時執行多個任務,併發編程對計算精密型沒有優點,反而因爲任務的切換使得效率變低。若是程序是IO精密型的,則因爲IO操做遠沒有CPU的計算速度快,因此讓程序阻塞於IO操做將浪費大量的CPU時間。若是程序有多個線程,則當前被IO操做阻塞的線程可主動放棄CPU,將執行權轉給其它線程。(*IO精密型和cpu精密型能夠參考此文:CPU-bound(計算密集型) 和I/O bound(I/O密集型)linux


併發編程主要有多線程和多進程,這裏咱們先討論併發模式,併發模式指:IO處理單元和多個邏輯直接協調完成任務的方法。服務器主要有兩種併發編程模式:編程

  • 半同步/半異步模式(half-sync/half-async)
  • 領導者/追隨者模式(Leader/Followers)

 2、半同步/半異步模式(half-sync/half-async)服務器

這裏的「同步」和「異步」和「IO」的「同步」「異步」是徹底不一樣的概念。在IO模型中,「同步」和「異步」區分的是內核嚮應用程序通知的是何種IO事件(是就緒事件仍是完成事件),以及該由誰來完成IO讀寫(是應用程序仍是內核)。在併發模式中,「同步」指的是程序徹底按照代碼序列的順序執行;「異步」指的是程序的執行須要由系統事件來驅動。常見的系統事件包括中斷、信號等。多線程

下圖1描述了併發模式同步讀操做(圖1a)和異步讀操做(圖1b)併發

   圖1併發模式同步讀(a)和異步讀(b)異步

已同步方式運行的線程爲同步線程,異步方式運行的爲異步線性,異步線程的執行效率高,實時性強,但編寫異步方式執行的程序相對複雜,難於調試和擴展,並且不適合於大量的併發。同步線程則相反,它雖然效率相對較低,實時性較差,但邏輯簡單。socket

所以對應服務器要求實時性及同時處理多個請求的程序,能夠同時使用同步線程和異步線程即採用半同步/半異步模式。同步線程用於處理客戶邏輯,異步線程用於處理IO事件。異步線程監聽到客戶請求後,就將其封裝成請求對象並插入到請求隊列中。請求隊列將通知某個工做在同步模式的工做線程來讀取並處理該請求對象。具體哪一個線性處理取決於請求隊列的設計。下圖2爲半同步/半異步的工做流程async

圖2半同步/半異步的工做流程函數

在半同步/半異步模式能夠變體成爲半同步/半反應堆(half-sync/half-reactive),以下圖3

圖3半同步/半反應堆模式

半同步/半反應堆中,異步線程只有一個,即主線程,他負責監聽全部事件,有事件發生則將事件插入請求隊列中。工做線程休眠在請求隊列中,當任務到來時,經過競爭獲取任務處理權

在上圖3半同步/半反應堆中,主線程插入工做隊列的爲就緒的鏈接socket,他要求工做線程本身socket讀取數據和往socket寫入服務器應答,全部能夠看做Reactor模式。實際也能夠模擬爲Proactor模式,即主線程完成數據的讀寫,將數據封裝成任務對象插入請求隊列,工做線程從請求隊列取出任務對象處理。(Reactor模式和Reactor模式能夠參考此文:服務器兩種高效的事件處理模式

半同步半反應堆模式存在以下缺點:

一、主線程和工做線程共享請求隊列,對請求隊列的操做需求加鎖,耗費CPU時間。

二、每個工做線程在同一時間只能處理一個客戶請求。客戶數量多,工做線程少,請求隊列任務堆積,響應滿,若是添加試圖經過增長線程則,因爲線程切換致使的CPU時間消耗。

這裏咱們再介紹一種高效的半同步/半異步模式:每一個工做線程都能同時處理多個客戶鏈接。

圖4 高效半同步/半異步模式


主線程只管理監聽socket,鏈接socket由工做線程來管理。當有新的鏈接到來時,主線程就接受之並將新返回的鏈接socket派發給某個工做線程,此後該socket上的任何IO操做都由被選中的工做線程來處理,直到客戶端關閉鏈接。主線程向工做線程派發socket的最簡單的方式,是往它和工做線程之間的管道里寫數據。工做線程檢測到管道里有數據可讀時,就分析是不是一個新的客戶鏈接請求到來。若是是,則把該新socket上的讀寫事件註冊到本身的epoll內核事件表中。
每一個線程(主線程和工做線程)都維持本身的事件循環,它們各自獨立的監聽不一樣的事件。所以在這種模式中,每一個線程都工做在異步模式,因此它並不是嚴格意義上的半同步半異步模式。

3、領導者/追隨者模式(Leader/Followers)

領導者/追隨者模式是多個工做線程輪流得到事件源集合,輪流監聽、分發並處理事件的一種模式。在任意時間點,程序都僅有一個領導者線程,它負責監聽IO事件。而其餘線程都是追隨者,它們休眠在線程池中等待成爲新的領導者。當前的領導者若是檢測到IO事件,首先要從線程池中推選出新的領導者線程,而後處理IO事件。此時,新的領導者等待新的IO事件,而原來的領導者則處理IO事件,兩者實現了併發。
包含以下幾個組件:

  • 句柄集(HandleSet)
  • 線程集(ThreadSet)
  • 事件處理器(EventHandler)
  • 具體的事件處理器(ConcreteEventHandler)。

關係以下圖5

圖5 領導者/追隨者模式的組件

一、句柄集

句柄表示IO資源,linux下一般是文件描述符。句柄集使用wait_for_event方法監聽這些句柄上的IO事件,並將其中的就緒事件通知給領導者線程。領導者調用綁定到Handle上的事件處理器來處理事件。綁定是經過句柄集的register_handle方法實現的。

二、線程集

全部工做線程的管理者,負責線程同步、推選新領導。線程在任一時間必處於如下三種狀態之一:

  • Leader:領導者線程,負責等待句柄集上的IO事件。
  • Processing:線程正在處理事件。領導者檢測到IO事件後能夠轉移至Processing狀態處理該事件,並調用promote_new_leader方法推選新領導者;也能夠指定其餘追隨者來處理事件,此時領導者地位不變。當處於Processing狀態的線程處理完事件後,若是當前線程集中沒有領導者,則它將成爲新領導者,不然它直接轉爲追隨者。
  • Follower:線程處於追隨者身份,經過調用線程集的join方法等待成爲新領導者,也可能被領導者指定來處理新的事件。

這三種狀態之間的轉換關係圖以下圖6:

圖6 領導者/追隨者模式的狀態轉移

注意,領導者推選新領導和追隨者等待成爲新領導這兩個操做都會修改線程集,所以線程集提供一個Synchronizer來同步。

三、事件處理器和具體的事件處理器

事件處理器一般包含一個或多個回調函數handle_event。這些回調函數用於處理事件對應的業務邏輯。事件處理器在使用前須要被綁定到某個句柄上,當該句柄有事件發生時,領導者就執行綁定的事件處理器的回調函數。具體的事件處理器是事件處理器的派生類。它們從新實現基類的handle_event方法,以處理特定的任務。

因爲領導者本身監聽IO事件並處理客戶請求,該模式不須要在線程間傳遞額外數據,也無需像半同步/半反應堆模式那樣在線程間同步對請求隊列的訪問。可是,該模式的明顯缺點是僅支持一個事件源集合,所以也沒法讓每一個工做線程獨立管理多個客戶鏈接。

咱們將領導者/追隨者模式的工做流程總結以下圖7

圖7 領導者/追隨者模式的工做流程

 

注(本文內容參考 Linux高性能服務器編程——第八章 遊雙著)

相關文章
相關標籤/搜索