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

[微軟技術白皮書] 這個秋天最熱門的是自由執行緒模型(Free Threading Model)

 
axsoft
版主


發表:681
回覆:1056
積分:969
註冊:2002-03-13

發送簡訊給我
#1 引用回覆 回覆 發表時間:2002-06-13 10:27:45 IP:61.220.xxx.xxx 未訂閱
來源:http://www.microsoft.com/taiwan/msdn/ 目前微軟已移除此文章!    David Platt David Platt 是 Rolling Thunder Computing 的總裁及創辦人 (www.rollthunder.com)。他也是 The Essence of OLE with ActiveX (Prentice Hall, 1996) 的作者。可以與 David 聯絡用 dplatt@rollthunder.com.    在多執行緒程式上面使用 COM 是一個充滿了害怕,不確定性,及懷疑的主題。如果您正在寫一個 COM 物件,您需要擔心關於對物件的成員函數及資料的連續存取嗎?物件伺服器的全域函數及資料變成如何了呢?而撰寫一個使用 COM 的用戶端應用程式又變成如何呢--應用程式可以從兩個執行緒存取同一個物件嗎?可以從同一個伺服器的兩個不同的執行緒存取兩個不同的物件嗎?又如果一個物件的表現方式從一個版本到另一個版本有所變更的時候呢?    請不要擔心。在大多數 COM 的主題中,事實並不像預測的那麼痛苦。如果您遵循一些簡單的規則,COM 將會處理好令人害怕的細節部分。    要求一個用戶對其想要使用的物件的內部有詳細的認識,或讓一個物件要顧慮到其用戶端的的內部,將會跟所有 COM 的基本原則有所衝突。COM 的 C 代表元件--若一個用戶端需要顧慮一個物件的內部,那麼這個物件不能真正稱為元件。COM 支援在任何一邊都不需要知道或顧慮另一邊在作什麼的方式執行緒。其遵循以下的原則運作:     用戶端應用程式在其經由 CoInitializeEx 函數初始化 COM 之時告訴 COM 其在使用執行緒之時所要遵循的規則,取代 CoInitialize。     物件的伺服器告訴 COM 其在使用執行緒之時要遵循的規則,不論當其呼叫CoInitializeEx 用於一個EXE 伺服器或經由製作登錄內容用於一個 DLL 伺服器。     當一個用戶端建立一個物件之時,COM 會比較每方面所宣稱其遵循的執行緒規則。如果各分面都保證經由相同的規則進行,那麼 COM     會在兩者之間設定一個直接的連接並且不干涉。如果他們遵循不同的規則,那麼 COM 會設定在兩方之間安排使得每方面都只看到其宣稱其知道如何處裡的執行緒規則。後面這個情況會耗費一些執行效能但是可以讓所有遵循不相同執行緒規則的各方面可以一起運作而不會有抱怨聲或需要一個特殊的方式。 執行緒模組選項 我知道有更多潛在的 COM 程式設計師被和所有其他視窗的術語不同的專業術語 "執行緒模型," "apartment 執行緒," 及 "自由執行緒" 給嚇退了。雖然無法改變的術語持續在變更,他們並沒有那麼糟。     一個執行緒模型是一組規則的集合用來描述一個物件或用戶端所遵循用於執行緒及 COM 之間的互動行為。我之前的文章,"Give ActiveX?-based Web Pages a Boost with the Apartment Threading Model," (MSJ,Febuary 1997),已經解釋過 apartment 執行緒模型,也就是所謂的單一執行緒 apartment 模型,一組可以被遵循的規則。這篇文章承續了 1997 年二月那篇文章所沒有提到的部分,所以如果您剛接觸到在 COM 之下執行緒的爭議的話,我強烈建議您從閱讀那篇文章開始。其可以在四月發行的 Microsoft Developer Network 光碟片中取得,或您可以訂閱 MSJ 過期的刊物經由他們的網站站台 (http://www.microsoft.com/msj)。    在 apartment 模型中,最主要的規則就是一個用戶端應用程式只能使用建立物件的執行緒呼叫一個物件的方法。這表示同一個伺服器的不同物件可以被不同的執行緒呼叫,但是每個物件只會被從一個執行緒呼叫。這個照順序表示物件必須將其對其伺服器的全域變數及函數作序列化的存取,但是對其本身的 instance 資料則不會如此。在 apartment 模型之下,物件相對地更容易寫作,但是用戶端可能更難處理。如果一個 apartment 執行緒用戶端需要從一個不同的執行緒呼叫一個物件的方法,他可以這樣做,但是其必須要跳過一些環節並且要先和作業系統協調。    這篇文章描述自由執行緒模型,也就是所謂的多執行緒 apartment 模型,這是另外一組物件或伺服器可能選擇遵循的規則。在自由執行緒模型中,一個用戶端應用程式可能會在任何時間從任何執行緒呼叫任何物件方法或 COM 函數。這端視物件序列存取到所有需要用來進來的呼叫會不會衝突。其提供最大的執行效能及彈性。代價是相對於 apartment 模型物件這些物件本身更難撰寫,雖然自由執行緒的用戶端應用程式比 apartment 執行緒的用戶端應用程式簡單。    使用自由執行緒模型比 apartment 模型要好的地方在哪裡?它是好在當有一個物件需要被從超過一個以上的執行緒存取時。假設您經由 DCOM 將一個用戶端應用程式連接到一個遠端的機器。當遠端用戶端呼叫那個物件的一個方法,伺服器會從一個為這個目的存在的集區中的一個現存的執行緒接收那個呼叫。這個接收執行緒使得本機的呼叫到真實的物件。如果物件支援自由執行緒模型,那麼就可以直接從接收執行緒中呼叫到物件裡面。如果物件支援 apartment 模型,那麼這個呼叫會被傳送到物件原來建立的執行的執行緒並且結果會在傳回到用戶端之前先傳送到接收執行緒。您引起您所不需要的額外處理花費。您也承擔物件正在服務其他物件的風險或--更糟的是--被在等待某些同步事件所阻擋。最後,您無法利用伺服器有多重中央處理器晶片的狀況,這變的越來越常見。    在另外一個範例中,假設您有一個單一物件代表一個到資料庫的連接,或可能是一個硬體的一部份。這個文章引用的範例程式模擬後面的狀況。假設您有四個或五個需要存取那個物件的執行緒。在 apartment 執行緒模型之下,物件可能置於,至多,這些執行緒其中之一。其他執行緒可能需要在起始的時候設立用來從他們本身的執行緒存取這個物件的執行緒間的處理代理人。您將會引發程式碼必須在起始的時候設立排列以及排列的耗費於每次您做呼叫的時候所得到的損失。如果物件及用戶端都支援自由執行緒模型,那麼多重執行緒全部可以直接連接到物件,而不用排列。物件本身需要將其本身的呼叫序列化到他們需要的範圍,而您會引發這個耗費,但是物件的程式設計師的對其表現方式的豐富知識使他可以將這個狀況加以最佳化。大多數時候,許多這個呼叫可能會是重複進入並且不需要將全部序列化。您真正必須要將常地序列化的可以比 COM 的通用排列器更便宜地完成。 自由執行緒用戶端應用程式 自由執行緒用戶端應用程式比 apartment 用戶端容易撰寫。一個 apartment 執行緒用戶端必須遵循每些有限制的規則關於哪些執行緒是被允許存取這些物件的。一個自由執行緒容器全部所需要     做的是製作一個到 API 函數 ConInitializeEx 的呼叫,傳送一個 COINIT_MULTITHREADED 的數值作為其第二個參數如圖 1 所示。不像 apartment 模型一樣,每個個別的執行緒不需要呼叫 CoInitializeEx;一個呼叫處理這個程序中的所有執行緒。這將 COM 在用戶端應用程式的位址空間起始化並且告訴他這個應用程式支援自由執行緒模型。一旦其這樣做之後,用戶端應用程式可能接著會在任何時間從任何執行緒呼叫任何 COM 函數或物件方法。    // constant _WIN32_DCOM 定義於計畫的設定項 在t WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, 在t nCmdShow) { /* 檢查 CoCreateInstanceEx() 函示是否存在於作業 系統 DLL Ole32.dll 中。如果沒有發現,那麼就是沒有支援自由執行緒模型。通知使用者並且離開。  */ HANDLE hMod = GetModuleHandle ("ole32.dll") ;  FARPROC fp = GetProcAddress (hMod, "CoCreateInstanceEx") ;  if (fp == NULL)  {  MessageBox (NULL, "This version of the operating system does not\  support the free threading model", "Error", MB_OK) ;  return -1 ;  }  /*  將 COM 初始化用於自由執行緒模型。  */  CoInitializeEx (NULL, COINIT_MULTITHREADED) ;  } 當用戶端應用程式建立一個物件之時,COM 會經由登錄內容偵測是否有任何物件無法以這個方式被呼叫。關於這些物件,COM 通透地設定排列以序列化存取其方法透過物件期待的方式。您的用戶端應用程式不需要考慮任何其所建立物件所使用的執行緒模型。您的應用程式告訴 COM 其所遵循的規則並且遵循之。COM 檢查物件所說其遵循的規則並且通透地在用戶端及物件之間仲裁,以使他們一起正常地運作。 這裡有兩件事是清楚的,都和自由執行緒模型是一個最近的發現有關的。首先,您必須在您的專案裡面定義常數 _WIN32_DCOM。在這篇文章所引用的範例中,我已經在專案設定中完成這個動作以確保其可以適用於任何地方。和 Visual C 5.0 一起來的系統 header 檔案跳過和 DCOM 有關的功能,例如 CoInitializeEx,除非這個旗標有被定義。 第二個清楚的是自由執行緒模組是存在於 Windows NT 4.0 及之後的版本,但其並不支援較早版本的 Windows NT,或 Windows 95 除非 DCOM 的延伸檔名已經被安裝過了。DCOM 副檔名是和 Internet Explorer 4.0 一起安裝的並且可以從微軟的網站站台分別取得。一個基於自由執行緒模型的用戶端應用程式必須在啟動的時候檢查正在執行的作業系統是否支援此模型。幸運地,這是很容易可以做到的;只要檢查 CoInitializeEx 函示是否存在於作業系統 DLL ole32.dll。如圖 1 所示,簡單地呼叫 API 函示並且取得 Module-Handle 並且取得 ProcAddress。如果 CoInitializeEx 是存在的,那麼自由執行緒模型在目前使用者的作業系統是有支援的;不然您的用程式應該要更幽雅地退出這個簡單的範例> 範例程式 在我深入探究這個範例程式之前,我強烈建議您下載這個程式碼並且在您閱讀這篇文章的時候和我一起做。這篇文章的文字是參照 freethread.zip 檔案中的範例應用程式,這個檔案可以從 MSJ 網站站台 (http://www.microsoft.com/msj) 取得。當您將這個檔案解壓縮之時,您將要尋找一個目錄,\freethread,包含一個用戶端應用程式於子目錄 \client,一個 in-proc 伺服器於子目錄 \inprocsv,及一個本機伺服器於目錄 \localsv。 在執行這個用戶端應用程式之前,您必須將伺服器註冊。要做到這件事,將 \freethread 目錄移動到您的 C: 磁碟機的根目錄並且在註冊檔案 C:\freethread\freethread.reg 上按兩下。如果您不想要將範例目錄置於這個位置,您可以將其放置到任何您想要的位置,但是您必須要編輯 freethread.reg檔案以變更所有其 LocalServer32 及 InprocServer32 的內容這樣他們會指向您實際上放置這些檔案的位置。 一個支援自由執行緒模型的 in-proc 伺服器經由新增稱為數值 ThreadingModel 到其 InProcServer32 登錄鍵值並且將其數值設定成 Free 來向 COM 表明自己的身份,如圖 2 所示。所引用的登錄檔至作用於四個不同類別的 ID 內容,每個表明本身支援允許的執行緒模型的一種 (無,apartment,自由,或兩者皆是)。這些的每個 InProcServer32 內容指向相同的伺服器 DLL。很明顯地,在每個狀況下為每個類別 ID 執行的程式碼是相同的,但是執行緒模型內容將會導致 COM 在處理執行緒的時候對他們有不同的方式。這個範例用戶端可以接著使用在不同的類別 ID 下建立的物件以闡明在 COM 中對於執行緒模型對待方式的不同。 In-proc 伺服器包含一個稱為 data6ips.dll的控制項。這是一個控制項於多數這個字的一般觀念:一個 In-proc 伺服器只支援 IDataObject 介面。 想向我是一個無線電接收器電路版的製造商,可以接收 National Bureau of Standards 時間訊號,無線電台 WWWV。(這是您的耶誕節清單裡面全部雜耍演員的完美禮物。) 我必須要提供一個軟體機制給應用程式以存取目前經由電路版接收到的銫時鐘時間。不使用一個直接的 DLL,我已經選擇經由一個 COM In-proc 伺服器提供之。實際的時間是經由方法 IDataObject::GetDate 用一個專屬的格式所稱為 TimeData-Object 所提供,這包含一個經由一個 HGLOBAL 傳送的 SYSTEMTIME 結構。這個論證更重要的一點是事實上這個物件支援另外一個稱為 ThreadId 的格式,這會簡單地傳回這個執行緒 ID,這個物件是經由這個執行緒 ID 接收這個呼叫。這個允許我在啟動一個呼叫的執行緒 ID 和接收物件中的執行緒之間作比較。這個方法的程式碼如圖 3 所示。 Figure 3 IDataObject::GetData extern UINT cfThreadId, cfTimeData,; STDMETHODIMP CTimeData::GetData (LPFORMATETC lpfe, LPSTGMEDIUM lpstg) { /* 我們支援專屬的剪貼簿格式經由 UINT cfThreadId 來辨識。呼叫者正在詢問我們用來接收呼叫的執行緒的 ID。配置一個新的全域變數,將我們的執行緒 ID 複製到其中,並且填入我們所傳送的 STGMEDIUM 結構的元件。 */ if (lpfe->cfFormat == cfThreadId && lpfe->tymed == TYMED_HGLOBAL && lpfe->dwAspect == DVASPECT_CONTENT) { DWORD *pdw ; pdw = (DWORD *)GlobalAlloc(GMEM_FIXED, sizeof (DWORD)); if (!pdw) { return E_OUTOFMEMORY ; } # *pdw = GetCurrentThreadId ( ) ; # lpstg->tymed = TYMED_HGLOBAL ; lpstg->hGlobal = (HGLOBAL) pdw ; lpstg->pUnkForRelease = NULL ; # return S_OK ; } /* 我們支援專屬的剪貼簿格式經由 UINT cfTimeDate 來辨識。這個實際上是一個 SYSTEMTIME 類型的結構。配置一個新的全域變數,將我們的全域系統時間複製到其中,並且填入我們所傳送的 STGMEDIUM 結構的元件。 */ if (lpfe->cfFormat == cfTimeData && lpfe->tymed == TYMED_HGLOBAL && lpfe->dwAspect == DVASPECT_CONTENT) { SYSTEMTIME *pSt ; # /* 如果 lindex == -1,那麼我們只要簡單地傳送一個 SYSTEMTIME 結構。 任何其他的數值代表我們正在執行一個效能測試。在這個狀況下, lindex 的數值是呼叫的應用程式測量反應時間要配置的記憶體數量。 */ if (lpfe->lindex == -1) { pSt = (SYSTEMTIME *)GlobalAlloc(GMEM_FIXED, sizeof (SYSTEMTIME)); } else { pSt = (SYSTEMTIME *)GlobalAlloc(GMEM_FIXED, lpfe->lindex); } if (!pSt) { return E_OUTOFMEMORY ; } # GetLocalTime (pSt) ; # lpstg->tymed = TYMED_HGLOBAL ; lpstg->hGlobal = (HGLOBAL) pSt ; lpstg->pUnkForRelease = NULL ; ; # eturn S_OK ; } /* 使用者要求一個我們無法支援的格式。傳回錯誤代碼。 */ return DATA_E_FORMATETC ; } 本機伺服器包含一個稱為 data6losv.exe的應用程式。其與 In-proc 伺服器一樣提供相同的物件到一個用戶端。其被提供用來證明有關只適用於 EXE 伺服器而不適用於 DLL 的觀念及特殊考量。 為了要證明多執行緒的觀念,這些範例伺服器不會為每個要求的用戶端建立一個個別的物件。相反的,他們建立一個新的物件供第一個要求的用戶端使用並且用一個現存物件的參考以提供所有後來的用戶端使用。這個可能不是一個最好的方式來撰寫一個真正的應用程式;我在這裡使用是為了證明的用途。然而,每些 COM 的部分明顯地是為這個目的而存在的,例如執行中物件表格。這並不是一個被禁止的結構。 用戶端應用程式是一個多執行緒的 MDI 應用程式稱為 data6cl.exe。主要框架視窗是經由應用程式的主要執行緒所處理。其包含三個 MDI 子視窗,每個都經由其個別的執行緒處理。
系統時間:2024-04-20 21:40:44
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!