??? 摘 要: CAR構(gòu)件進行遠程調(diào)用" title="遠程調(diào)用">遠程調(diào)用的原理和過程,針對該過程中服務器端的效率問題提出了進程級線程池解決辦法,構(gòu)建了進程級線程池模型,實現(xiàn)了關(guān)鍵部分的代碼。最后結(jié)合一個簡單的構(gòu)件遠程服務示例說明了其工作過程。
??? 關(guān)鍵詞: CAR構(gòu)件? 進程級線程池? 遠程調(diào)用
?
??? 20世紀80年代以來,目標指向型軟件編程技術(shù)有了很大的發(fā)展,為大規(guī)模的軟件協(xié)同開發(fā)以及軟件標準化、軟件共享、軟件運行安全機制等提供了理論基礎(chǔ)。其發(fā)展可以大致分為面向?qū)ο?/a>" title="面向?qū)ο?>面向?qū)ο?/a>編程、面向構(gòu)件編程、面向中間件編程三個階段。
??? CAR技術(shù)就是總結(jié)面向?qū)ο缶幊獭⒚嫦驑?gòu)件編程技術(shù)的發(fā)展歷史和經(jīng)驗,為更好地支持面向以Web服務為代表的下一代網(wǎng)絡(luò)應用軟件開發(fā)而發(fā)明的。CAR為構(gòu)件軟件和應用程序之間進行通信提供了統(tǒng)一的標準,它為構(gòu)件程序提供了一個面向?qū)ο蟮幕顒迎h(huán)境。
1 CAR構(gòu)件技術(shù)及CAR遠程構(gòu)件調(diào)用的基本原理
??? CAR(Component Application Run-Time)是一個具有國內(nèi)自主知識產(chǎn)權(quán)的構(gòu)件系統(tǒng),由上海科泰世紀科技有限公司開發(fā)。CAR構(gòu)件技術(shù)定義了一套網(wǎng)絡(luò)編程時代的構(gòu)件編程模型和編程規(guī)范,它規(guī)定了一組構(gòu)件間相互調(diào)用的標準,使得二進制構(gòu)件能夠自描述及在運行時動態(tài)鏈接。
1.1 CAR遠程接口自動列集/散集技術(shù)
??? CAR是面向?qū)ο蟮能浖P?,對象是它的基本要素之一。使用對象的應?或另一個對象)稱為客戶,有時也稱為對象的用戶。對象和客戶之間的相互作用建立在客戶/服務器模型的基礎(chǔ)上。
??? 當客戶端" title="客戶端">客戶端和服務器端所在地址空間不同時,客戶端的進程要調(diào)用服務器端的構(gòu)件服務,屬于遠程構(gòu)件調(diào)用。CAR構(gòu)件技術(shù)支持遠程接口調(diào)用,通過調(diào)用數(shù)據(jù)的列集/散集技術(shù)進行不同地址空間的數(shù)據(jù)交互。構(gòu)件服務和構(gòu)件服務調(diào)用者可以處于操作系統(tǒng)的不同空間,而調(diào)用者可以如同在同一地址空間中使用構(gòu)件一樣透明地進行遠程接口調(diào)用,也就是說完全向用戶屏蔽了底層使用標準的列集/散集過程。
??? CAR的自動列集/散集主要用于和欣操作系統(tǒng)(Elastos),它在構(gòu)件的調(diào)用過程中的地位類似于COM的自動列集/散集。用戶如果采用默認的列集/散集過程,則使用一個遠程接口如同使用一個本地接口一樣,完全屏蔽了數(shù)據(jù)的交換、傳遞過程。
??? Elastos2.0以存根/代理機制來實現(xiàn)遠程接口自動列集/散集,主要涉及到三個對象,即處于客戶端的代理(Proxy)對象,處于服務端的存根(stub)對象,以及處于內(nèi)核的(Object)對象。
??? 一個客戶端進程不一定只調(diào)用一個遠程構(gòu)件服務。為了更方便有效地與各個遠程構(gòu)件交互數(shù)據(jù),Elastos在客戶端為每一個對應的遠程服務建立一個代理對象,記錄客戶進程的信息、遠程服務構(gòu)件對象的信息以及調(diào)用狀態(tài)等,負責客戶進程與對應的遠程服務聯(lián)系。
??? Elastos會為每個提供遠程服務的構(gòu)件對象建立一個存根對象,客戶端代理不是直接與遠程提供服務的構(gòu)件對象聯(lián)系,而是與存根對象聯(lián)系,通過存根對象來調(diào)用構(gòu)件對象。
??? 內(nèi)核Object對象是聯(lián)系客戶端和服務器端的樞紐,保持了相關(guān)服務的信息以及創(chuàng)建對象代理所需要的一些信息。它的建立標志著用戶可以通過某種方式遠程獲得相關(guān)服務(服務的發(fā)布)。
??? CAR的自動列集/散集通過在程序運行過程中動態(tài)生成存根/代理實現(xiàn)。一個用戶的遠程構(gòu)件調(diào)用首先通過代理對象轉(zhuǎn)發(fā)到內(nèi)核相應的Object對象,Object對象尋找到相應的服務進程以及存根對象,啟動某一個服務線程并將調(diào)用轉(zhuǎn)發(fā)給存根對象,然后再由存根對象去完成調(diào)用構(gòu)件方法的過程。而調(diào)用返回的過程正好與這個流程相反。圖1是對象流程調(diào)用的一個簡單示意圖。

1.2 客戶端調(diào)用遠程服務步驟
??? 當確定代理、存根都存在,并且從客戶端到服務器端建立一條可以相互通信的通路后,客戶端就可以開始遠程調(diào)用了。其流程如下:
??? (1)客戶端用戶的一次CAR遠程調(diào)用會轉(zhuǎn)發(fā)到代理對象上,代理對象將調(diào)用棧中的數(shù)據(jù)根據(jù)元數(shù)據(jù)" title="元數(shù)據(jù)">元數(shù)據(jù)信息打包,并傳給內(nèi)核;
??? (2)內(nèi)核根據(jù)注冊信息找到服務及存根對象,并將打包的信息傳給存根對象;
??? (3)存根對象根據(jù)元數(shù)據(jù)將數(shù)據(jù)解包,并構(gòu)建和客戶端調(diào)用棧相應的棧內(nèi)數(shù)據(jù),并調(diào)用真正的構(gòu)件服務接口函數(shù)" title="接口函數(shù)">接口函數(shù);
??? (4)服務構(gòu)件接口函數(shù)完成調(diào)用,并返回;
??? (5)存根對象獲取接口函數(shù)調(diào)用的參數(shù)信息及返回信息, 并將其打包,通過系統(tǒng)調(diào)用返回到內(nèi)核;
??? (6)內(nèi)核將服務器端的返回信息傳遞給客戶端,客戶端從系統(tǒng)調(diào)用返回;
??? (7)代理對象獲得返回信息,根據(jù)元數(shù)據(jù)解包并回填到用戶調(diào)用棧中。整個遠程構(gòu)件方法調(diào)用過程完成。
??? 圖2是按照數(shù)據(jù)流程的形式給出的一個進程間遠程調(diào)用的全部過程。

??? 從圖2可以看出,在服務進程內(nèi),服務最終是通過服務線程來提供的。很明顯,上述過程可能存在一個問題:一個進程可能提供不止一種服務,所以可能在服務進程內(nèi)需要多次重復地創(chuàng)建服務線程來為客戶端服務。線程的創(chuàng)建、銷毀和調(diào)度本身是有代價的,如果一個線程的任務相對簡單,則時間和空間開銷便不容忽視;如果一個服務進程同時對外提供多個服務,這種開銷會顯著地降低服務效率,從而導致整個系統(tǒng)的效率降低。對于Elastos這樣的嵌入式實時操作系統(tǒng)來說,這是非常致命的。
2 進程級線程池模型
??? 由上面的分析可以看出,問題的關(guān)鍵在于對線程資源的低效管理。對于共享資源,有一個很著名的設(shè)計模式:資源池(Resource Pool)。該模式正是為了解決資源的頻繁分配﹑釋放所造成的問題。為解決上述問題,可以采用線程池技術(shù),這里的線程池基于進程級。
2.1 線程池技術(shù)的原理
??? 線程池采用預創(chuàng)建技術(shù),在應用程序啟動之后,將立即創(chuàng)建一定數(shù)量的線程(N),并放入空閑隊列中。這些線程都是處于阻塞(Suspended)狀態(tài),不消耗CPU,但占用較小的內(nèi)存空間。當任務到來后,緩沖池選擇一個空閑線程,把任務傳入此線程中運行。當N個線程都在處理任務時,緩沖池便自動創(chuàng)建一定數(shù)量的新線程,用于處理更多的任務。在任務執(zhí)行完畢后,線程也不退出,而是繼續(xù)保持在池中等待下一次任務。當系統(tǒng)比較空閑時,大部分線程一直處于暫停狀態(tài),線程池自動銷毀一部分線程,回收系統(tǒng)資源。
??? 基于這種預創(chuàng)建技術(shù),線程池將線程創(chuàng)建和銷毀本身所帶來的開銷分攤到了各個具體任務上,執(zhí)行次數(shù)越多,每個任務所分擔到的線程本身開銷則越小。
??? 面向?qū)ο缶幊讨?,?chuàng)建和銷毀對象很費時間,因為創(chuàng)建一個對象要獲取內(nèi)存資源或者其他更多資源。所以提高服務程序效率的一個手段就是盡可能減少創(chuàng)建和銷毀對象的次數(shù),特別是一些耗費大量資源的對象創(chuàng)建和銷毀。如何利用已有對象來服務是一個需要解決的關(guān)鍵問題,也是為什么要設(shè)計線程池的原因。
??? 針對線程的特點,可將一般的線程使用過程的時間分段為:
??? T1——創(chuàng)建線程的時間
??? T2——在線程中執(zhí)行任務的時間
??? T3——線程銷毀的時間
??? 則完成一個任務所需時間T=T1+T2+T3。
對于Elastos中完成ezAPI調(diào)用的線程而言,T2所占用的時間相對于T1、T3較短,T2中包含的時間僅為一個接口函數(shù)執(zhí)行的時間。可以看出,線程本身的開銷所占的比例為(T1+T3)/(T1+T2+T3)。如果線程執(zhí)行的時間很短,則這筆開銷可能占到20%-50%左右;如果任務執(zhí)行時間很頻繁,則這筆開銷將不可忽略。所以線程池在Elastos操作系統(tǒng)中的使用,理論上可以顯著改善系統(tǒng)性能。圖3是其簡單的原理圖。

??? 從圖中可以看出,線程池中的線程(池線程)同普通的線程無異,實現(xiàn)上屬于同類數(shù)據(jù)結(jié)構(gòu)。但是對于線程池中的線程,其所需資源已經(jīng)分配并初始化,池線程只用在棧中構(gòu)建好ezAPI調(diào)用的相關(guān)棧布局,就可以直接調(diào)用了。
2.2 進程級線程池模型的實現(xiàn)
??? 在模型實現(xiàn)過程中,主要涉及三個類:進程類、線程類、線程池類。線程池對象作為進程數(shù)據(jù)結(jié)構(gòu)的一部分而存在,一個進程對應一個線程池,每個線程池中存在0~MaxThreadNum個線程。當進程初創(chuàng)建時,線程池中的線程數(shù)為0。
2.2.1 線程池類定義
class ThreadPool {
public:
void Initialize(CProcess*pOwner);
ECODE GetThread(Thread**ppThread,ScheduleClass*pScheduleClass,uint_t uSchedulePolicy);//獲取線程池中線程
void PutBackThread(Thread*pThread);//將執(zhí)行完任務的
//線程回收
inline Thread*FindThread(ScheduleClass*pScheduleClass,
uint_t uSchedulePolicy);//尋找適合線程
inline void SetCapacity(UINT uCapacity);//設(shè)置線程池容量
~ThreadPool( );
private:
ECODE CreateThread(Thread**ppThread,ScheduleClass?鄢pScheduleClass,uint_t uSchedulePolicy);//在線程池中創(chuàng)建
//新線程
void DestroyThread(Thread*pThread);
public:
CProcess* m_pOwner;//線程池所在的進程對象
UINT??m_uCapacity;//線程池容量
DLinkNode?m_threadList;//線程池中的空閑線程隊列
UINT??m_cThreads;//線程池中的空閑線程數(shù)量
KMutex??m_threadLock;//線程池互斥變量,用于同步
};
2.2.2 進程類中有關(guān)線程池的代碼
class CProcess:
{
public:
CARAPI_(ULONG) AddRef(void);
CARAPI_(ULONG) Release(void);
CARAPI Start(/*[in]*/ WString wsName, /*[in]*/ WString wsArgs);
CARAPI Kill( );
public:
ThreadPool?m_threadPool;//每個進程均有一個線程池
ProcessHContext m_hcontext;
};
2.2.3 進程類與線程類中關(guān)于線程池操作部分的代碼
INLINE ECODE CProcess∷GetThread(Thread**ppThread)
//獲得線程
{
if(ProcessState_Finished==m_processState && this !=∷GetCurrentProcess()) {
??return E_PROCESS_ALREADY_EXITED;
}
return m_threadPool.GetThread(ppThread,pScheduleClass,uSchedulePolicy);
}
ULONG Thread∷Release(void)?//銷毀線程
{
if (ThreadState_Running!=m_uPoolThreadState) {
??if (1==lRef && !this->IsFinished() && m_pOwner)
???m_pOwner->m_threadPool.PutBackThread(this);
??else if (0==lRef) {
???m_pOwner->m_threadPool.m_threadLock.Lock();
???m_inProcLink.Detach();
???m_pOwner->m_threadPool.m_threadLock.Unlock();
??}
??delete this;
}
else assert(0 !=lRef);
return (ULONG)lRef;
}
??? 使用上面描述的線程池模型后,進程間遠程調(diào)用的過程就發(fā)生了變化,內(nèi)核空間部分的步驟7由原來的在服務進程中直接創(chuàng)建新的線程變成了從服務進程的線程池中直接獲取一個空閑線程來提供服務,如圖4所示。

3 線程池應用舉例
??? 下面以一個簡單的遠程調(diào)用實例說明上面所描述的線程池模型的應用。
??? 假設(shè)現(xiàn)在有三個CAR構(gòu)件:car1.dll、car2.dll、car3.dll,分別提供構(gòu)件對象obj1、obj2、obj3,每個對象中實現(xiàn)接口函數(shù)如下:
obj1∷server1( )
{
?printf(″this is the first service!\n″);
}
obj2∷server2( )
{
??printf(″this is the second service!\n″);
}
obj3∷server3( )
{
??printf(″this is the third service!\n″);
}
現(xiàn)有一個服務進程如下:
void main( )
{
??IObject*svr1 = new obj1( );
??IObject*svr2 = new obj2( );
??IObject*svr3 = new obj3( );
??EzRegisterService(server1,svr1);//注冊服務1
??EzRegisterService(server2,svr2);//注冊服務2
??EzRegisterService(server3,svr3);//注冊服務3
??wait( );
??EzUnregisterService(server1);//注銷服務1
??EzUnregisterService(server2);//注銷服務2
??EzUnregisterService(server3);//注銷服務3
??svr1.delete( );
??svr2.delete( );
??svr3.delete( );
??……
??return;
}
??? 該進程通過三個構(gòu)件提供三種服務,每種服務提供一個簡單的打印功能。
??? 這時有三個客戶進程分別如下:
void main( )
{
??IObject*host1;
??EzFindService(L″server1″,&host1);
//查詢服務1
??host1->server1();//提供服務1
??……
??return;
}
void main()
{
??IObject*host2;
??EzFindService(L″server2″,&host2);//查詢服務2
??host2->server2();//提供服務2
??……
??return;
}
void main()
{
??IObject*host3;
??EzFindService(L″server3″,&host3);//查詢服務3
??host3->server3();//提供服務3
??……
??return;
}
??? 上述過程中,當三個客戶進程并發(fā)進行時,服務進程就需要為每一種服務創(chuàng)建一個服務線程以提供服務,這時就會利用線程池來創(chuàng)建所用的線程。這就大大提高了服務效率,從而提高了整個系統(tǒng)的性能。
??? 線程池致力于減少線程本身的開銷對應用所產(chǎn)生的影響,但前提是線程本身開銷與線程執(zhí)行任務相比不可忽略。如果線程本身的開銷相對于線程任務執(zhí)行開銷而言可以忽略不計,則此時線程池所帶來的好處不明顯。例如對于FTP服務器以及Telnet服務器,通常傳送文件的時間較長,開銷較大,則此時采用線程池未必是理想的方法,而可以選擇“即時創(chuàng)建,即時銷毀”的策略。
??? 在構(gòu)件遠程調(diào)用過程中,服務進程中線程池的設(shè)計與實現(xiàn)可以大大提高服務效率。本文通過簡要介紹CAR構(gòu)件遠程調(diào)用的原理,引出了該過程中存在的效率問題,然后針對問題提出了進程級線程池的模型,并給出了一個簡單的實現(xiàn),通過該線程池能夠極大地提高構(gòu)件遠程調(diào)用中服務器端的服務效率。
??? 該線程池是在進程級別實現(xiàn)的,并沒有實現(xiàn)系統(tǒng)級線程池。為了在兩種方案及典型的應用情況下取得最好的服務性能,還應該進一步實現(xiàn)系統(tǒng)級線程池,并根據(jù)典型應用制定測試用例,對兩種方案進行效率上的比較,從而從中選取一種更優(yōu)的解決方案。
參考文獻
[1] KORETIDE.Elastos 2.0 Operating System Manual[M/CD].http://www.koretide.com.cn,2004/2005,6.
[2] KORETIDE.CAR′s Manual[M/CD].http://www.koretide.com.cn,2004/2005,6.
[3] BOVET D P,CESATI M.Understanding the Linux Kernel,2nd Edition[M].Sebastopol:O′Reilly,2002:22-35.
[4] BUTENHOF D R.Programming with POSIX Threads.AddisonWesley,1997.
[5] KRIEMANN R.Implementation and Usage of a Thread Pool based on POSIX Threads Max-Planck-Institute for Mathematics in the Sciences.2004-10-19
[6] CALCOTE J.Thread pools and server performance.Dr.Dobb′s Juurnal,1997,7:60-64.
[7] NICHOLS B,BUTTLAR D,F(xiàn)ARRELL J P.Pthreads Programming.O′Reilly & Associates,Sebastopol,CA,1996.
