一、并發(fā)編程與并發(fā)模式
并發(fā)編程主要是為了讓程序同時(shí)執(zhí)行多個(gè)任務(wù),并發(fā)編程對(duì)計(jì)算精密型沒有優(yōu)勢(shì)吧史,反而由于任務(wù)的切換使得效率變低鼓鲁。如果程序是IO精密型的蕴轨,則由于IO操作遠(yuǎn)沒有CPU的計(jì)算速度快,所以讓程序阻塞于IO操作將浪費(fèi)大量的CPU時(shí)間骇吭。如果程序有多個(gè)線程橙弱,則當(dāng)前被IO操作阻塞的線程可主動(dòng)放棄CPU,將執(zhí)行權(quán)轉(zhuǎn)給其它線程。
IO精密型和cpu精密型可以參考此文:CPU-bound(計(jì)算密集型) 和I/O bound(I/O密集型)
并發(fā)編程主要有多線程和多進(jìn)程燥狰,這里我們先討論并發(fā)模式棘脐,并發(fā)模式指:IO處理單元和多個(gè)邏輯直接協(xié)調(diào)完成任務(wù)的方法。服務(wù)器主要有兩種并發(fā)編程模式:
- 半同步/半異步模式(half-sync/half-async)
- 領(lǐng)導(dǎo)者/追隨者模式(Leader/Followers)
二龙致、半同步/半異步模式(half-sync/half-async
這里的“同步”和“異步”和“IO”的“同步”“異步”是完全不同的概念蛀缝。在IO模型中,“同步”和“異步”區(qū)分的是內(nèi)核向應(yīng)用程序通知的是何種IO事件(是就緒事件還是完成事件)目代,以及該由誰來完成IO讀寫(是應(yīng)用程序還是內(nèi)核)屈梁。在并發(fā)模式中,“同步”指的是程序完全按照代碼序列的順序執(zhí)行榛了;“異步”指的是程序的執(zhí)行需要由系統(tǒng)事件來驅(qū)動(dòng)在讶。常見的系統(tǒng)事件包括中斷、信號(hào)等霜大。
下圖1描述了并發(fā)模式同步讀操作(圖1a)和異步讀操作(圖1b)
已同步方式運(yùn)行的線程為同步線程真朗,異步方式運(yùn)行的為異步線性,異步線程的執(zhí)行效率高僧诚,實(shí)時(shí)性強(qiáng)遮婶,但編寫異步方式執(zhí)行的程序相對(duì)復(fù)雜蝗碎,難于調(diào)試和擴(kuò)展,而且不適合于大量的并發(fā)旗扑。同步線程則相反蹦骑,它雖然效率相對(duì)較低,實(shí)時(shí)性較差臀防,但邏輯簡(jiǎn)單眠菇。
因此對(duì)應(yīng)服務(wù)器要求實(shí)時(shí)性及同時(shí)處理多個(gè)請(qǐng)求的程序,可以同時(shí)使用同步線程和異步線程即采用半同步/半異步模式袱衷。同步線程用于處理客戶邏輯捎废,異步線程用于處理IO事件。異步線程監(jiān)聽到客戶請(qǐng)求后致燥,就將其封裝成請(qǐng)求對(duì)象并插入到請(qǐng)求隊(duì)列中登疗。請(qǐng)求隊(duì)列將通知某個(gè)工作在同步模式的工作線程來讀取并處理該請(qǐng)求對(duì)象。具體哪個(gè)線性處理取決于請(qǐng)求隊(duì)列的設(shè)計(jì)嫌蚤。下圖2為半同步/半異步的工作流程
在半同步/半異步模式可以變體成為半同步/半反應(yīng)堆(half-sync/half-reactive)辐益,如下圖3
半同步/半反應(yīng)堆中,異步線程只有一個(gè)脱吱,即主線程智政,他負(fù)責(zé)監(jiān)聽所有事件,有事件發(fā)生則將事件插入請(qǐng)求隊(duì)列中箱蝠。工作線程休眠在請(qǐng)求隊(duì)列中续捂,當(dāng)任務(wù)到來時(shí),通過競(jìng)爭(zhēng)獲取任務(wù)處理權(quán)宦搬。
在上圖3半同步/半反應(yīng)堆中牙瓢,主線程插入工作隊(duì)列的為就緒的連接socket,他要求工作線程自己socket讀取數(shù)據(jù)和往socket寫入服務(wù)器應(yīng)答床三,所有可以看作Reactor模式。實(shí)際也可以模擬為Proactor模式杨幼,即主線程完成數(shù)據(jù)的讀寫撇簿,將數(shù)據(jù)封裝成任務(wù)對(duì)象插入請(qǐng)求隊(duì)列,工作線程從請(qǐng)求隊(duì)列取出任務(wù)對(duì)象處理差购。(Reactor模式和Reactor模式可以參考此文:服務(wù)器兩種高效的事件處理模式)
半同步半反應(yīng)堆模式存在如下缺點(diǎn):
- 主線程和工作線程共享請(qǐng)求隊(duì)列四瘫,對(duì)請(qǐng)求隊(duì)列的操作需求加鎖,耗費(fèi)CPU時(shí)間欲逃。
- 每一個(gè)工作線程在同一時(shí)間只能處理一個(gè)客戶請(qǐng)求找蜜。客戶數(shù)量多稳析,工作線程少洗做,請(qǐng)求隊(duì)列任務(wù)堆積弓叛,響應(yīng)滿,如果添加試圖通過增加線程則诚纸,由于線程切換導(dǎo)致的CPU時(shí)間消耗撰筷。
這里我們?cè)俳榻B一種高效的半同步/半異步模式:每個(gè)工作線程都能同時(shí)處理多個(gè)客戶連接。
主線程只管理監(jiān)聽socket畦徘,連接socket由工作線程來管理毕籽。當(dāng)有新的連接到來時(shí),主線程就接受之并將新返回的連接socket派發(fā)給某個(gè)工作線程井辆,此后該socket上的任何IO操作都由被選中的工作線程來處理关筒,直到客戶端關(guān)閉連接。主線程向工作線程派發(fā)socket的最簡(jiǎn)單的方式杯缺,是往它和工作線程之間的管道里寫數(shù)據(jù)蒸播。工作線程檢測(cè)到管道里有數(shù)據(jù)可讀時(shí),就分析是否是一個(gè)新的客戶連接請(qǐng)求到來夺谁。如果是廉赔,則把該新socket上的讀寫事件注冊(cè)到自己的epoll內(nèi)核事件表中。每個(gè)線程(主線程和工作線程)都維持自己的事件循環(huán)匾鸥,它們各自獨(dú)立的監(jiān)聽不同的事件蜡塌。因此在這種模式中,每個(gè)線程都工作在異步模式勿负,所以它并非嚴(yán)格意義上的半同步半異步模式馏艾。
三、領(lǐng)導(dǎo)者/追隨者模式(Leader/Followers
領(lǐng)導(dǎo)者/追隨者模式是多個(gè)工作線程輪流獲得事件源集合奴愉,輪流監(jiān)聽琅摩、分發(fā)并處理事件的一種模式。在任意時(shí)間點(diǎn)锭硼,程序都僅有一個(gè)領(lǐng)導(dǎo)者線程房资,它負(fù)責(zé)監(jiān)聽I(yíng)O事件。而其他線程都是追隨者檀头,它們休眠在線程池中等待成為新的領(lǐng)導(dǎo)者轰异。當(dāng)前的領(lǐng)導(dǎo)者如果檢測(cè)到IO事件,首先要從線程池中推選出新的領(lǐng)導(dǎo)者線程暑始,然后處理IO事件搭独。此時(shí),新的領(lǐng)導(dǎo)者等待新的IO事件廊镜,而原來的領(lǐng)導(dǎo)者則處理IO事件牙肝,二者實(shí)現(xiàn)了并發(fā)。包含如下幾個(gè)組件:
- 句柄集(HandleSet)
- 線程集(ThreadSet)
- 事件處理器(EventHandler)
- 具體的事件處理器(ConcreteEventHandler)。
關(guān)系如下圖5
1配椭、句柄集
句柄表示IO資源虫溜,linux下通常是文件描述符。句柄集使用wait_for_event方法監(jiān)聽這些句柄上的IO事件颂郎,并將其中的就緒事件通知給領(lǐng)導(dǎo)者線程吼渡。領(lǐng)導(dǎo)者調(diào)用綁定到Handle上的事件處理器來處理事件。綁定是通過句柄集的register_handle方法實(shí)現(xiàn)的乓序。
2寺酪、線程集
所有工作線程的管理者,負(fù)責(zé)線程同步替劈、推選新領(lǐng)導(dǎo)寄雀。線程在任一時(shí)間必處于以下三種狀態(tài)之一:
- Leader:領(lǐng)導(dǎo)者線程,負(fù)責(zé)等待句柄集上的IO事件陨献。
- Processing:線程正在處理事件盒犹。領(lǐng)導(dǎo)者檢測(cè)到IO事件后可以轉(zhuǎn)移至Processing狀態(tài)處理該事件,并調(diào)用promote_new_leader方法推選新領(lǐng)導(dǎo)者眨业;也可以指定其他追隨者來處理事件急膀,此時(shí)領(lǐng)導(dǎo)者地位不變。當(dāng)處于Processing狀態(tài)的線程處理完事件后龄捡,如果當(dāng)前線程集中沒有領(lǐng)導(dǎo)者卓嫂,則它將成為新領(lǐng)導(dǎo)者,否則它直接轉(zhuǎn)為追隨者聘殖。
- Follower:線程處于追隨者身份晨雳,通過調(diào)用線程集的join方法等待成為新領(lǐng)導(dǎo)者,也可能被領(lǐng)導(dǎo)者指定來處理新的事件奸腺。
這三種狀態(tài)之間的轉(zhuǎn)換關(guān)系圖如下圖6:
(注意餐禁,領(lǐng)導(dǎo)者推選新領(lǐng)導(dǎo)和追隨者等待成為新領(lǐng)導(dǎo)這兩個(gè)操作都會(huì)修改線程集,因此線程集提供一個(gè)Synchronizer來同步突照。)
3帮非、事件處理器和具體的事件處理器
事件處理器通常包含一個(gè)或多個(gè)回調(diào)函數(shù)handle_event。這些回調(diào)函數(shù)用于處理事件對(duì)應(yīng)的業(yè)務(wù)邏輯讹蘑。事件處理器在使用前需要被綁定到某個(gè)句柄上末盔,當(dāng)該句柄有事件發(fā)生時(shí),領(lǐng)導(dǎo)者就執(zhí)行綁定的事件處理器的回調(diào)函數(shù)衔肢。具體的事件處理器是事件處理器的派生類庄岖。它們重新實(shí)現(xiàn)基類的handle_event方法豁翎,以處理特定的任務(wù)角骤。
由于領(lǐng)導(dǎo)者自己監(jiān)聽I(yíng)O事件并處理客戶請(qǐng)求,該模式不需要在線程間傳遞額外數(shù)據(jù),也無需像半同步/半反應(yīng)堆模式那樣在線程間同步對(duì)請(qǐng)求隊(duì)列的訪問邦尊。但是背桐,該模式的明顯缺點(diǎn)是僅支持一個(gè)事件源集合,因此也無法讓每個(gè)工作線程獨(dú)立管理多個(gè)客戶連接蝉揍。
我們將領(lǐng)導(dǎo)者/追隨者模式的工作流程總結(jié)如下圖7
注(本文內(nèi)容參考 Linux高性能服務(wù)器編程——第八章 游雙著)