全國最多中醫師線上諮詢網站-台灣中醫網
發文 回覆 瀏覽次數:2608
推到 Plurk!
推到 Facebook!

多執行緒的應用程式伺服器如何設定

缺席
shchen
一般會員


發表:12
回覆:26
積分:7
註冊:2003-08-13

發送簡訊給我
#1 引用回覆 回覆 發表時間:2010-11-02 22:05:15 IP:202.173.xxx.xxx 訂閱
小弟接觸三層式架構應用程式約6個月了,手上開發了4支三層式的應用系統,應用的層面不大,使用者約為10~20人同時連上DCOM SERVER.
漸漸發覺,若其中1位USER從DATABASE匯出大量資料時,其它USER會需等待,造成效率有時很差.
目前市面上對於多層式架構應用的書籍很少,更沒有針對BCB使用多層式開發的書,我僅能研究李維大的delphi 4.x 實戰篇,第6章及第9章其中有提到,在建立Remote data module時,即使是選擇了Apartment model 執行緒模式,實際在應用程式SERVER上,不論幾位USER連線到APP SERVER,而APP SERVER始終是以一個執行緒在做服務(先不談論APP SERVER與SQL SERVER間的TSession間的關系).
書中有提到delphi 4.0已將能夠控制APP SERVER 執行緒的程式碼寫好了,只需使用demos\Midas\pooler 子目錄內的thrddCF 檔案,並將原本使用TComponentFactory類別的宣告換成thrddCF中的TThreadedClassFactory,才能將APP SERVER 做成多執行緒的模式,讓每個使用者在APP SERVER.EXE運作上是各自擁有一個獨立的Thread.


問題來了,我始終在BCB建立的專案內找不到TComponentFactory類別的宣告的使用,更別說將DEMO檔內的thrddCF方法導入BCB使用.
所以想請問熟悉BCB 三層架構的前輩們給個解答或指引.
小弟是使用RAD Studio 2010內的BCB 2010.

怎麼都沒有人知道呢?是我提供的資訊不足嗎?還是我發錯版了?
編輯記錄
shchen 重新編輯於 2010-11-02 08:06:54, 註解 無‧
shchen 重新編輯於 2010-11-09 01:11:06, 註解 無‧
shchen
一般會員


發表:12
回覆:26
積分:7
註冊:2003-08-13

發送簡訊給我
#2 引用回覆 回覆 發表時間:2010-12-01 22:38:36 IP:202.173.xxx.xxx 訂閱
終於找到相同問題的解答了,分享日後有燃眉之急的人,:注意下方.h檔的修改行號會因不有BCB版本而異,若找不到就用搜尋的,找到就對了!
文章出處來自www.cngr.cn
用C Builder建立 多執行緒 COM伺服器.
Sunspot Lee

一、線程、Apartment和進程

說道COM的線程模型,大家就會想到各種Apartment模型。但Apartment究竟是什麼?如何建立一個Apartment呢?

Apartment就是線程的容器,線程中有關COM的操作必須在Apartment中進行。 Apartment分為STA和MTA兩種,STA是只能容納一個線程的容器,MTA是能容納多個線程的容器。 COM規定,一個進程中可以有多個STA,但最多只能有一個MTA。線程調用CoInitializeEx(NULL,COINIT_APARTMENTTHREADED)後,這個線程就建立並且進入了一個STA,線程調用CoInitializeEx(NULL,COINIT_MULTITHREADED)後,這個線程就進入了進程公用MTA。一個線程不能同時進入兩個Apartment。線程調用CoUninitialize()後,這個線程就退出了它所在的Apartment。設計COM對象時設定的“Apartment模型”就是指這個COM對象可以呆在那種Apartment中。一個線程建立的COM對象自動地呆在這個線程所在的Apartment中。要是這個線程建立了很多個COM對象,那這些對像都呆在這個線程所在的Apartment中。

一個線程可以直接訪問它所在的Apartment中的COM對象,但要訪問另一個Apartment中的COM對象就必須經過調度。因為STA中只有一個線程,別的線程要訪問這個線程建立的COM對象就必須讓這個線程代勞了,如此一來,對這個Apartment中所有的COM對象的訪問都是序列化的,這些COM對象就不用擔心有好幾個線程同時訪問它的麻煩事。 MTA中的COM對象就沒這麼舒服了,它們必須考慮到可能會有好幾個線程同時訪問它們。 MTA之外的一個線程訪問MTA中的一個COM對象時,系統會從COM系統線程池中取出一個線程進入MTA,由它來代表客戶線程訪問這個COM對象。 (COM系統線程池的機理是怎麼樣的?池中有幾個線程?)



二、客戶與服務器

COM對象位於服務器中,服務器分為進程內服務器、進程外服務器、遠程服務器三種。進程內服務器是一個DLL文件,進程外服務器是一個EXE文件,遠程服務器是另一台計算機上的一個DLL文件或EXE文件。遠程服務器如果是一個DLL文件的話,由一個被稱為“Surrogate”的代理程序調用它。

進程內服務器中的COM對象的Apartment模型如果與客戶線程所在的Apartment相配合的話,客戶線程建立COM對象時會直接建立在客戶線程所在的Apartment中。比如Apartment模型與STA、Free模型與MTA,Both模型與STA或MTA。這樣客戶線程就可以直接調用COM對象而不用調度。否則就會專門建立一個線程,然後由這個線程建立COM對象,COM對象和客戶線程就分處在兩個Apartment中。進程外服務器和遠程服務器中的COM對像一定不會建立在客戶線程所在的Apartment中。對它們的調用一定要經過調度的。



三、在C Builder下建立一個多Apartment的進程外服務器

由於不必考慮並行的問題,COM對像一般設成使用Apartment線程模型。進程內服務器還沒什麼問題,如果你試著建了一個進程外服務器,並且讓幾個客戶同時訪問服務器中的對象的話,就會發現這些訪問不是同時進行的。如果有一個訪問特別費時間,它後面的訪問就要等很久才能進行。這是因為服務器中只有一個STA,雖然每個線程都建立了自己的COM對象,但這些對像都在這個STA中,當然無法並行執行。

克服這個問題的辦法很簡單,打開Borland\CBuilder5\Include\Atl\Atlmod.h文件,把第271行(for 2010)的:

typedef TATLModule TComModule;

改成:

#ifdef __DLL__

typedef TATLModule TComModule;

#else

typedef TATLModule > TComModule;

#endif

再打開Borland\CBuilder5\Include\Atl\Atlcom.h文件,把第3244行(For 2010)的:

DECLARE_CLASSFACTORY()

改成:

#ifdef __DLL__

DECLARE_CLASSFACTORY()

#else

DECLARE_CLASSFACTORY_AUTO_THREAD()

#endif

就可以了。重新編譯你的程序,同時開兩個客戶試一試,是不是並發執行了?

先別高興得太早,如果你同時開了五個客戶,並且其中四個在執行費時的訪問,你就會發現第五個客戶的訪問要等待一段時間。這種現象與C Builder的實現代碼有關。

作了前面的修改後,服務器啟動後會預先生成幾個線程,這些線程各自進入一個STA中。當服務器接到客戶的訪問要求後,會循環指定一個線程負責這個客戶的建立COM對象、訪問COM對象的事務。

比如第一個客戶要求建立一個COM對象,服務器就給一號線程發消息,讓這個線程建立一個COM對象並把這個COM對象的接口傳給客戶,以後第一個客戶對這個COM對象的訪問就全由一號線程代理。而第二個客戶的建立COM對象、訪問COM對象的事務就由服務器指定二號線程來辦,如果客戶太多,線程用完了,服務器又會讓一號線程負責客戶的要求,依次循環。如果客戶很多,線程可能會負責幾個客戶的訪問要求,而由同一個線程服務的客戶的訪問就會順序執行。預先生成的線程數缺省為系統的CPU個數乘以四,也就是四個(除非你的機器有好幾個CPU)。

只能同時服務四個客戶當然是不行的,讓我們繼續修改。打開主CPP文件,可以看到下面兩行代碼:

TComModule ProjectModule(0);

TComModule &_Module = ProjectModule;

改為:

TComModule ProjectModule(MyInitATLServer);

TComModule &_Module = ProjectModule;

其中“MyInitATLServer”是一個新加的函數,定義如下:

void __fastcall MyInitATLServer()

{

if (_Module.SaveInitProc)

_Module.SaveInitProc();



_Module.Init(ObjectMap, Sysinit::HInstance, NULL, 6);//注意這個6

_Module.m_ThreadID = ::GetCurrentThreadId();

_Module.m_bAutomationServer = true;
_Module.DoFileAndObjectRegistration();

AddTerminateProc(_Module.AutomationTerminateProc);

}

看到那個6沒有,這代表服務器啟動後會預先生成6個線程,也就能同時服務6個客戶。這個6可以改成別的數,當然不要太大了,不然機器垮了可別怪我。

改到現在你可能比較滿意了,但其實這個服務器還是有缺陷:一開始就生成所有線程是不是太浪費了?循環分配線程好像也不太合理,更重要的是,如果客戶程序中途垮了,沒有Release它建立的COM對象,那這個COM對象將一直存在下去,佔用的資源無法收回。

要解決這些問題就比較麻煩了,建議大家看一看ATL源代碼,編寫自己的TComModule類和CComThreadAllocator類。



四、編寫多線程客戶程序時要注意的問題

建立客戶程序時必須包含的*_ATL.h文件中有一個很好的COM對象包裝類。比如我建立了一個ComLib服務器,裡面有一個MyComObj對象,那麼在ComLib_ATL.h文件中有一個TCOMIMyComObj類,它很好的封裝了MyComObj對象。寫單線程程序時可以這樣建立它:

TCOMIMyComObj aComObj = CoMyComObj::CreateInstance();

(CoMyComObj是定義在在ComLib_ATL.h文件中的一個輔助類)然後就可以使用aComObj了,不必調用CoInitializeEx()和CoUninitialize(),也不必釋放aComObj。假設MyComObj對像中定義了一個方法fun(),一個屬性num,可以這樣使用:

aComObj.fun();

aComObj.num = 14;

int val = aComObj.num;

注意到num的訪問方法了嗎? C Builder靈活運用了特有的__property關鍵字,不必調用get_num()和set_num()了。

如果在寫多線程客戶程序時也這樣就會出問題:除了第一個線程正常外,後面的的線程無法建立COM對象了。

問題出在CoMyComObj裡面,它保證了會調用CoInitializeEx()和CoUninitialize()並且在整個進程中只會調用一次。而在多線程客戶程序中,每個線程都必須調用CoInitializeEx()和CoUninitialize()一次。因此,除了第一個線程成功進入了Apartment,別的線程都失敗了。

可以這樣建立TCOMIMyComObj對象:

CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

IMyComObj *pComObj;

OleCheck(CoCreateInstance(CLSID_MyComObj, NULL, CLSCTX_LOCAL_SERVER

, IID_IMyComObj, (void **)(&pComObj)));

TCOMIComObjInExe aComObj(pComObj);

……使用aComObj……

CoUninitialize();

注意,這段代碼必須寫在TThread::Execute()中,因為只有TThread::Execute()裡的代碼才是真正運行在新線程中的。另外決不能調用pComObj->Release()。



後記

學COM的念頭起於看李維寫的那三本書中的第一本的時候,李維描述了建立多線程服務器的重要性,但具體方法只是一筆帶過。後來我看了Delphi帶的例子,想用在C Builder中,卻無從下手。在關於COM的部分,Delphi和C Builder相差太大了,而又沒有這方面的C Builder的書,網上的資料也很少,只好自己摸索。期間曾在網上發貼提問,總是沒人回答,痛苦啊!
系統時間:2017-12-15 12:33:01
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!