◆汪利虎 張凡石 金 京
(1.中國人民解放軍31011部隊(duì) 北京 100091;2.中國電科集團(tuán)28所 江蘇 210007)
基于“管家模型”的異步消息通知與任務(wù)調(diào)度機(jī)制
◆汪利虎1張凡石2金 京1
(1.中國人民解放軍31011部隊(duì) 北京 100091;2.中國電科集團(tuán)28所 江蘇 210007)
本文提出了基于“管家模型”的異步消息通知與任務(wù)調(diào)度機(jī)制,主要用于在包含大量計算和業(yè)務(wù)相關(guān)性的應(yīng)用系統(tǒng)中通過異步消息的方式簡化隨機(jī)任務(wù)調(diào)度與處理。通過在安卓操作系統(tǒng)上通過充分利用系統(tǒng)特性進(jìn)行了輕量級的實(shí)現(xiàn),驗(yàn)證了此模型的可行性及給開發(fā)工作帶來的便捷性。
異步;多線程
為了應(yīng)對軟件用戶日益復(fù)雜的功能及體驗(yàn)需求,應(yīng)用系統(tǒng)的設(shè)計正面臨著越來越多的挑戰(zhàn),多任務(wù)的管理尤為突出。在現(xiàn)代操作系統(tǒng)已經(jīng)能夠非常好的支持多任務(wù)的背景下,如何充分利用其“多核”等特性,開發(fā)出符合客戶復(fù)雜需求但足夠穩(wěn)定和健壯的應(yīng)用,一直是軟件業(yè)界的熱門研究課題。
通常,實(shí)現(xiàn)多任務(wù)的方式有多進(jìn)程與多線程兩種,表1列出了這兩種方式在數(shù)據(jù)共享、同步、資源占用、生命周期管理和開發(fā)調(diào)試等方面的對比情況。
表1 多進(jìn)程與多線程實(shí)現(xiàn)方式對比情況
在包含大量計算和業(yè)務(wù)相關(guān)性的應(yīng)用系統(tǒng)中進(jìn)行隨機(jī)任務(wù)的調(diào)度與處理,意味著較多的任務(wù)執(zhí)行模塊可能被毫無規(guī)律的、突發(fā)性的或頻繁的創(chuàng)建與銷毀,他們的生命周期可能很短暫,并且這些任務(wù)執(zhí)行模塊需要和應(yīng)用程序的主模塊通信以傳輸任務(wù)處理結(jié)果或告知其狀態(tài)。在此背景下,本文提出了基于“管家模型”的異步消息通知與任務(wù)調(diào)度機(jī)制。
為簡化隨機(jī)任務(wù)的調(diào)度處理,并解決軟件系統(tǒng)開發(fā)中的線程管理和維護(hù)較易混亂的情況,本文借鑒生活中主人把事務(wù)交由管家代理并由各個下屬仆人去執(zhí)行的模式,將其應(yīng)用到多線程的管理中來,并以此建立了“管家模型”。模型的整體架構(gòu)如圖1所示。
在大部分的應(yīng)用系統(tǒng)與開發(fā)框架中(如Microsoft .NET框架和Android操作系統(tǒng)),對用戶操作的響應(yīng)和處理是通過事件驅(qū)動的。UI線程通常作為應(yīng)用的主線程自系統(tǒng)啟動后就一直運(yùn)行并消費(fèi)(Producer/Consumer模式)運(yùn)行時的各種事件,如:鼠標(biāo)點(diǎn)擊(OnClick)事件,觸屏(OnTouch)事件等。如果主線程(UI線程)執(zhí)行過多的與UI渲染無關(guān)的操作和任務(wù),可能導(dǎo)致UI無法響應(yīng)用戶與其的交互(ANR,Application Not Responding),為解決這個問題,必須將一些耗時費(fèi)力的操作交由子線程處理,并且鑒于方便管理和統(tǒng)籌規(guī)劃方面的考慮,在所有的子線程中設(shè)置兩種角色:調(diào)度線程一個(管家)及工作線程若干(仆人)。主線程不需向各個子線程交辦具體工作任務(wù),而是通過調(diào)度線程下達(dá)命令,為使任務(wù)結(jié)果能夠更快回傳,各個子線程直接向主線程匯報任務(wù)處理情況,而他們的執(zhí)行狀態(tài)、生命周期等主線程不關(guān)心的信息則匯報給調(diào)度線程并由其管理和維護(hù)。
圖1 “管家模型”的整體架構(gòu)
(1)主線程
當(dāng)一個應(yīng)用程序啟動時,操作系統(tǒng)將會為其創(chuàng)建一個進(jìn)程,同時一個線程也會立即運(yùn)行在此進(jìn)程中,該線程就是程序的主線程,主線程通常就是創(chuàng)建GUI的線程(UI線程)。因此,主線程是整個應(yīng)用程序運(yùn)行最主要的模塊,執(zhí)行程序的主要邏輯,負(fù)責(zé)繪制UI呈現(xiàn)給用戶并且直接或間接處理所有與用戶的交互。
在事件驅(qū)動的系統(tǒng)中,主線程自創(chuàng)建并運(yùn)行后,會自動擁有一個消息循環(huán)及與其綁定的消息隊(duì)列以存儲未能及時處理的事件,消息隊(duì)列中的事件將會被消息循環(huán)迅速、順序的分發(fā)和處理。而對不同類型的事件做出相應(yīng)處理以減輕主線程負(fù)擔(dān),加快其消息分發(fā)速度,避免其長時間無響應(yīng),這正是調(diào)度線程和工作線程存在的意義。
由于一些任務(wù)的行為是固化的,他們獨(dú)立于具體的使用場景,因此主線程可以直接通過一些預(yù)先定義的命令字向調(diào)度線程發(fā)送這些固定的任務(wù),這些命令字可舉例如下:
①TASK_INIT:啟動和初始化調(diào)度管理模塊并將初始化后續(xù)的工作交由其進(jìn)一步處理;
②TASK_STOP:結(jié)束調(diào)度管理模塊的生命周期,并將善后處理工作交由其進(jìn)一步處理;
③TASK_RESTART:由于某些原因造成調(diào)度管理模塊工作異常,需要重新啟動并初始化。
其余具體的任務(wù)將會以任務(wù)包和與其綁定標(biāo)識的形式發(fā)送給調(diào)度線程,當(dāng)主線程收到工作線程直接發(fā)來的任務(wù)結(jié)果時,可以按照之前的任務(wù)標(biāo)識識別他們,并且做出相應(yīng)反應(yīng),如更新UI元素或顯示一個短暫提示等等。
(2)調(diào)度線程
調(diào)度線程在主線程與工作線程之間扮演著協(xié)調(diào)者或代理的角色:代理所有來自主線程的任務(wù),維護(hù)所有的工作線程,協(xié)調(diào)他們之間的通信。主線程只需要向調(diào)度線程發(fā)送預(yù)先定義好的命令字或發(fā)起一個帶有字符串形式標(biāo)識的任務(wù)包,此任務(wù)包將會被預(yù)處理和進(jìn)一步封裝,然后根據(jù)當(dāng)前維護(hù)的工作線程狀態(tài),隨機(jī)選擇一個空閑線程接收和執(zhí)行此任務(wù)包。
與主線程類似,調(diào)度線程也會在自己內(nèi)部維護(hù)一個消息隊(duì)列(異步)以接收來自主線程及工作線程的各種消息,同時一個消息循環(huán)也會在線程啟動后被綁定以便從消息隊(duì)列中獲取和分發(fā)消息,通??梢栽诖讼⒀h(huán)內(nèi)部定義一個接口用來方便外部與其交互,如發(fā)送消息和定義消息處理方法等。需要注意的是,線程的啟動可以根據(jù)實(shí)際情況選擇“懶加載”(Lazy Init)的啟動模式,即在應(yīng)用程序第一次需要進(jìn)行多任務(wù)處理時才啟動此調(diào)度線程及相關(guān)的工作線程,這樣處理的好處是可以避免計算資源的空置浪費(fèi),而計算資源一旦被啟用后便可以復(fù)用以提高利用效率。此外,在調(diào)度線程內(nèi)部創(chuàng)建一個調(diào)度器,將不同的線程調(diào)度策略和機(jī)制統(tǒng)一管理、方便維護(hù),甚至在應(yīng)用程序運(yùn)行期間,可根據(jù)上下文環(huán)境實(shí)時置換策略(Strategy Pattern)。
(3)工作線程
工作線程在整個架構(gòu)模型中負(fù)責(zé)具體的任務(wù)執(zhí)行。這些線程資源被放入一個線程組中供管家按需調(diào)度,每個工作線程的生命周期由調(diào)度線程管理,并且根據(jù)調(diào)度線程發(fā)送的固定命令字或任務(wù)包做出響應(yīng),其運(yùn)行狀態(tài)的變化將及時通知上級(Status Update),即調(diào)度線程,如“忙->空閑”。如果工作線程被分配了具體任務(wù),任務(wù)執(zhí)行結(jié)束的結(jié)果直接上報給任務(wù)發(fā)送源,即主線程,以避免不必要的信息中轉(zhuǎn)。
通常,工作線程組中線程的數(shù)量是一個實(shí)踐經(jīng)驗(yàn)參數(shù),在不同的生產(chǎn)環(huán)境和框架下會有不同的配置,并且這些配置項(xiàng)都可以根據(jù)工程實(shí)際需要修改。例如,在Web應(yīng)用服務(wù)器Tomcat中,默認(rèn)的最小活躍線程數(shù)量參數(shù)minSpareThreads為25;在Java IoC框架Spring中,默認(rèn)的核心線程數(shù)量參數(shù)corePoolSize為1。
Android是一種基于Linux的開源操作系統(tǒng),是當(dāng)今全球市場份額最大的移動終端操作系統(tǒng)。移動應(yīng)用程序在運(yùn)行期間經(jīng)常需要處理用戶發(fā)起的短小隨機(jī)的計算任務(wù),如從網(wǎng)絡(luò)下載多個圖片,從本地數(shù)據(jù)庫讀取大量數(shù)據(jù)并解析等??梢姡芗夷P头浅_m用于此類應(yīng)用程序與用戶的交互場景,事實(shí)上管家模型的思想也是受此啟發(fā)而產(chǎn)生。
(1)Message:消息,其中包含了消息ID,消息處理對象以及處理的數(shù)據(jù)等,由MessageQueue統(tǒng)一列隊(duì),終由Handler處理。映射為管家模型中的異步消息Msg。
(2)Handler:處理者,負(fù)責(zé)Message的發(fā)送及處理。使用Handler時,需要實(shí)現(xiàn)handleMessage(Message msg)方法來對特定的Message進(jìn)行處理,例如更新UI等。Handler會引用當(dāng)前線程里的Looper和MessageQueue,同一線程可以有多個Handler,并且他們都共享同一Looper和MessageQueue。
(3)MessageQueue:消息隊(duì)列,每個線程只有一個,用來存放Handler發(fā)送過來的消息,并按照FIFO規(guī)則執(zhí)行。當(dāng)然,存放Message并非實(shí)際意義的保存,而是將Message以鏈表的方式串聯(lián)起來的,等待Looper的抽取。映射為管家模型中的消息隊(duì)列。
(4)Looper:消息泵,每個線程只有一個,不斷地從MessageQueue中抽取Message執(zhí)行。因此,MessageQueue與Looper一一對應(yīng)。映射為管家模型中的消息循環(huán)。
(5)Thread:線程,負(fù)責(zé)調(diào)度整個消息循環(huán),即消息循環(huán)的執(zhí)行場所。
(6)HandlerThread: Thread類的增強(qiáng),內(nèi)部封裝了消息循環(huán)供創(chuàng)建Handler以方便的進(jìn)行消息發(fā)送和處理。映射為管家模型中的調(diào)度線程和工作線程。
(7)MasterHandler: 綁定到調(diào)度線程的 Handler,對來自主線程和工作線程的不同類型消息作出相應(yīng)處理,包括進(jìn)步封裝和轉(zhuǎn)發(fā),更新內(nèi)部維護(hù)數(shù)據(jù)的狀態(tài),定期進(jìn)行狀態(tài)報告等。
如圖2所示,將上述的管家模型映射為一種示例實(shí)現(xiàn)并作出了相應(yīng)分析。(鑒于篇幅原因,此處只列出核心代碼片段,其余代碼已被刪除)
圖2 代碼清單1
圖3 代碼清單2
如圖 2所示,整個管家模型的核心功能被集中到MultiTaskManager類中,此類被實(shí)現(xiàn)為單例模式以保證全局唯一且一致的調(diào)用,并且在MultiTaskManager中保存了用以維護(hù)各工作線程狀態(tài)的 threadStateMap,參與整個模型中消息發(fā)送和處理handler的 handlerMap,以及來自主線程的各種類型任務(wù)的taskMap。
圖4 代碼清單3
圖5 代碼清單4
如圖3所示,startTask()方法展示了通過一個方法調(diào)用并傳遞異步消息、返回消息處理handler和具體任務(wù)(通過一個Runnable接口實(shí)現(xiàn))的方式,在全局范圍內(nèi)調(diào)用MultiTaskManager以完成多任務(wù)調(diào)度,任務(wù)發(fā)送方只需向外指派任務(wù)和處理任務(wù)結(jié)果,而不需要關(guān)心底層諸如線程創(chuàng)建和銷毀、計算資源的復(fù)用、數(shù)據(jù)同步等等一系列細(xì)節(jié)。
如圖4所示,展示了調(diào)度線程的消息處理機(jī)制中較為重要的幾個消息類型的處理方式:
(1)針對狀態(tài)報告STATE_BUSY,STATE_IDLE,更新內(nèi)部維護(hù)的數(shù)據(jù)狀態(tài)并繼續(xù)其他操作;
(2)針對初始化指令 TASK_INIT,將指令進(jìn)步傳遞給工作線程組以完成整個系統(tǒng)初始化過程;
(3)針對任務(wù)調(diào)度指令 TASK_SCHEDULE,根據(jù)自身狀態(tài)和可用資源情況執(zhí)行調(diào)度操作。
如圖5所示,展示了任務(wù)如何調(diào)度的一種簡要操作方式,其關(guān)鍵步驟如下:
(1)獲取當(dāng)前空閑的計算資源,即工作線程,若失敗,采用特定退避算法進(jìn)行重新調(diào)度;
(2)封裝任務(wù)行為,工作線程在執(zhí)行任務(wù)之前(不論任務(wù)是否為空)將總是首先更新自身狀態(tài),并報告調(diào)度線程其狀態(tài)更新;
(3)若任務(wù)不為空,執(zhí)行任務(wù);
(4)封裝任務(wù)行為,工作線程在執(zhí)行任務(wù)完畢后更新自身狀態(tài)并報告調(diào)度線程其狀態(tài)更新;
(5)直接向任務(wù)發(fā)送源(通常就是主線程)傳遞任務(wù)結(jié)果。
圖6 “管理模型”在Android系統(tǒng)下實(shí)現(xiàn)的具體運(yùn)行過程
圖6通過一個從數(shù)據(jù)庫加載大量數(shù)據(jù)的實(shí)例工程展示了上述管家模型在 Android系統(tǒng)下實(shí)現(xiàn)的具體運(yùn)行過程,包含了從 UI線程發(fā)起的多任務(wù)管理模塊的全局初始化、計算資源獲取、任務(wù)調(diào)度與執(zhí)行和返回任務(wù)結(jié)果等過程。由代碼清單及圖2可見,多任務(wù)管理模塊的使用非常簡潔,流程清晰。
在這種“管家模型”中,系統(tǒng)中的不同角色通過規(guī)范化的異步消息(可統(tǒng)一抽象為消息標(biāo)識和消息內(nèi)容)進(jìn)行通信,彼此之間獨(dú)立運(yùn)行,各自維護(hù)內(nèi)部的邏輯和狀態(tài),因此系統(tǒng)的整體穩(wěn)定性大幅提高,不會因某個子模塊崩潰而導(dǎo)致整個系統(tǒng)崩潰,這種模型的設(shè)計更符合軟件工程多年實(shí)踐以形成的思想:“高內(nèi)聚,松耦合”。
[1]阮曉星,楊亮,魏晉鵬.基于Intranet的事務(wù)處理系統(tǒng)[J].微計算機(jī)應(yīng)用,1997.
[2]唐麗,趙強(qiáng).基于CORBA實(shí)現(xiàn)分布式系統(tǒng)間的異步消息通信[J].計算機(jī)應(yīng)用,2002.
[3]高鵬飛,李新明,孫建.Linux與VxWorks任務(wù)調(diào)度機(jī)制分析[J].工業(yè)控制計算機(jī),2005.
[4]http://tomcat.a(chǎn)pache.org/.
[5]http://spring.io/.