[转]COM线程模型-套间
[轉]COM線程模型-套間
COM線程模型-套間來源: http://blog.csdn.net/crybird/archive/2008/10/11/3057067.aspx 查找了好多資料,終于對套件這一概念有一點心得,趕緊記錄下來。
首先,只要遵守COM規(guī)范,不用COM庫也能編寫COM程序,那相當于自己實現用到的COM庫函數。本篇COM如果單獨出現,指COM庫。
1 進程、線程回顧
《WINDOWS核心編程》對進程和線程有深入解釋,一個程序運行起來,需要一個進程作為容器。進程管理所有系統(tǒng)資源、內存、線程等等。線程是CPU的調度單位,有自己的棧和寄存器狀態(tài)。程序最初創(chuàng)建的線程叫主線程,主線程可以創(chuàng)建子線程,子線程還可以創(chuàng)建子線程。
不同進程之間是無法直接通信的,因為它們在虛擬內存中的地址不一樣。但操作系統(tǒng)通過LPC機制,可以完成不同進程之間的通信。
COM在進程間通信的方法是本地過程調用(LPC),因為操作系統(tǒng)知道各個進程的確切邏輯地址,所以操作系統(tǒng)可以完成這一點。不同進程間傳遞的參數需要調整,LPC技術可以完成普通數據的直接拷貝(甚至包括自定義類和指針),但對于接口參數,COM實現了IMarshal接口以調整。
為了可以用同樣的方式和進程外、遠程組件通信,客戶端不直接和組件通信,而是和代理/存根通信,代理/存根是(而且必須是) DLL形式,能完成參數調整和LPC調用。代理存根不用自己寫,系統(tǒng)會自動產生。
注:接口的調整,包括列集和散集兩種marshal/unmarshal。
2 COM線程模型
2.1 分清模型與實現
看過《Inside C++ Object Model》(中文名:深入C++對象模型;侯捷譯)的人都知道,C++對象模型有三種,各家編譯器都選擇其中效率最高的一種實現出來。另外兩種就留在了理論世界,實現出來沒有太大意義。提這個的原因,就是為了弄清楚這一點:COM線程模型只是理論構想,是一種抽象的數學模型,還要COM庫通過各種手段實現出來,才能為我們使用。
2.2 套間的由來
最開始的COM庫,支持的使用組件的唯一模式是single-thread-per-process模式。這樣就避免了多線程的同步,而且組件執(zhí)行的線程肯定是創(chuàng)建它的線程。
然而組件對象真正的執(zhí)行環(huán)境很復雜。COM組件的執(zhí)行環(huán)境有兩種:單線程環(huán)境Single-Thread,多線程環(huán)境Multi-Thread。單線程要考慮執(zhí)行線程是否是創(chuàng)建組件的線程;多線程還要考慮并發(fā)、同步、死鎖、競爭等問題。無論哪種環(huán)境,都要編寫大量的代碼以使COM組件對象正確的運行。
為了使程序員減輕痛苦,COM庫決心提供一套機制來幫助程序員。如果我們都遵從這套機制,只要付出較少的勞動,就可以讓組件對象和COM庫一起完成工作。COM庫這套機制的核心技術就是“套間技術”。
2.3 COM的多線程模型
2.3.1 COM庫的規(guī)定
關于多線程問題方面,COM庫做出了如下規(guī)則(不是COM標準,是COM庫為了簡化多線程編程中對組件的調用而制定的):
1. COM庫提供兩種套間,單線程套間和多線程套間,COM組件的編寫者最好提供對應的屬性(后面會提到),COM組件的使用者要在套間里創(chuàng)建和調用組件。
2. COM庫對所有的調用進行參數調整(如果需要),不管是對進程內服務器的調用,還是對進程外服務器的調用。
3. 線程內調用、跨線程調用、跨進程調用都用統(tǒng)一的方式。需要用代理的會用代理。
如此COM規(guī)定了COM庫、組件編寫者、組件使用者三方合作關系。COM庫進行協(xié)調關系,會根據組件的能力,在不同環(huán)境(套間)中創(chuàng)建和調用組件;編寫者要說明組件可以生存的環(huán)境;調用者查詢接口,合理調用。
2.3.2 單線程套間STA
Single-threaded Apartments,一個套間只關聯一個線程,COM庫保證對象只能由這個線程訪問(通過對象的接口指針調用其方法),其他線程不得直接訪問這個對象(可以間接訪問,但最終還是由這個線程訪問)。
COM庫實現了所有調用的同步,因為只有關聯線程能訪問COM對象。如果有N個調用同時并發(fā),N-1個調用處于阻塞狀態(tài)。對象的狀態(tài)(也就是對象的成員變量的值)肯定是正確變化的,不會出現線程訪問沖突而導致對象狀態(tài)錯誤。
注意:這只是要求、希望、協(xié)議,實際是否做到是由COM決定的。這個模型很像Windows提供的窗口消息運行機制,因此這個線程模型非常適合于擁有界面的組件,像ActiveX控件、OLE文檔服務器等,都應該使用STA的套間。
2.3.3 多線程套間MTA
Multithreaded Apartments,一個套間可以對應多個線程,COM對象可以被多個線程并發(fā)訪問。所以這個對象的作者必須在自己的代碼中實現線程保護、同步工作,保證可以正確改變自己的狀態(tài)。
這對于作為業(yè)務邏輯組件或干后臺服務的組件非常適合。因為作為一個分布式的服務器,同一時間可能有幾千條服務請求到達,如果排隊進行調用,那么將是不能想象的。
注意:這也只是一個要求、希望、協(xié)議而已。
2.3.4 COM+新增NA
COM+為了進一步簡化多線程編程,引入了中立線程套間概念。
NA/TNA/NTA,Neutral Apartment/Thread Neutral Apartment / Neutral Threaded Apartment。這種套間只和對象相關聯,沒有關聯的線程,因此任何線程都可以直接訪問里面的對象,不存在STA的還是MTA的。
2.4 到底什么是套間
根據《COM技術內幕》的觀點,COM沒有定義自己新的線程模型,而是直接利用了Win32線程,或者說對其做了改造、包裝。線程間的同步也是直接用的Win32 APIs。
《COM本質論》設這樣定義的:套間定義了一組對象的邏輯組合,這些對象共享一組并發(fā)性和沖入限制。每個COM對象都屬于某一個套間,一個套間可以包含多個COM對象。
MSDN上解釋說,可以把進程中的組件對象想象為分成了很多組,每一組就是一個套間。屬于這個套間的線程,可以直接調用組件,不屬于這個套間的線程,要通過代理才能調用組件。
最直接的說,COM庫為了實現簡化多線程編程的構想,提出了套間概念。套間是一個邏輯上的概念,它把Win32里的線程、組件等,按照一定的規(guī)則結合在一起,并且以此提供了一種模式,用于多線程并發(fā)訪問COM組件方面。可以把套間看作COM對象的管理者,它通過調度,切換COM對象的執(zhí)行環(huán)境,保證COM對象的多線程調用正常運行。COM和線程不是包含關系,而是對應和關聯關系。
3 第一方COM庫:模型的實現
3.1 單線程套間STA
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);這句代碼創(chuàng)建了一個STA,然后套間把當前的線程和自己關聯在一起,線程被標記為套間線程,只有這個線程能直接調用COM對象。
在創(chuàng)建套間的時候,COM創(chuàng)建了一個隱藏的窗口。關聯線程對組件的調用直接通過接口指針調用方法;其他線程對套間里的對象的調用,都轉變成對那個隱藏窗口發(fā)送消息,然后由這個隱藏窗口的消息處理函數來實際調用組件對象的方法。編寫組件代碼的時候,只需調用DispatchMessage即可將方法調用的消息和普通的消息區(qū)分開來(通過隱藏窗口的消息處理函數)。
由于窗口消息的處理是異步的,所以所有的調用都是依次進行的,不必考慮同步的問題。只要調用的時候,參數進行合理調整即可(COM庫會做到這一點)。但是對于全局變量和靜態(tài)變量,組件編寫者還是要費心的。
一個STA只關聯一個線程, single-thread-per-process模式只是STA的一個特例。使用這種模式的線程叫套間線程(Apartment Thread)。
3.2 多線程套間MTA
CoInitializeEx(NULL, COINIT_MULTITHREADED);第一次如此調用的時候,會創(chuàng)建一個MTA,然后套間把當前線程和自己關聯在一起,線程被標記為自由線程。以后第二個線程再調用(在同一進程中)的時候,這個MTA會把第二個線程也關聯在一起,并標記為自由線程。一個MTA可以關聯多個線程。
所有的關聯線程都可以調用套間中的組件。這就涉及到同步問題,需要組件編寫者解決。
一個MTA可以關聯一個或多個線程,這種模式下,COM組件自己要考慮同步問題。使用這種模式的這些線程叫做自由線程(Free Thread) 。
3.3 總結
一個進程可以有0個、1個或多個STA,還可以有0個或1個MTA。
一個線程,進入(或創(chuàng)建)套間后,不能改變套間模式;但可以退出套間,然后以另外的模式再進入(或創(chuàng)建)另一個套間。
在一個進程中,主套間是第一個被初始化的。在單線程的進程里,這是唯一的套間。調用參數在套間之間被調整,COM庫通過消息機制處理同步。
如果你設計多個線程作為自由線程,所有的自由線程在同一個單獨的套間中,參數被直接(不被列集)傳遞給這個套間的任何線程,而且你要處理所有的同步。
在既有自由線程又有套間線程的進程里,所有自由線程在一個套間里,而其他套間都是單線程套間。而進程是包含一個多線程套間和N個單線程套間的容器。
COM的線程模型為客戶端和服務器提供了這樣一種機制:讓不同的線程協(xié)同工作。不同進程內,不同線程之間的對象調用也是被支持的。以調用者的角度來看,所有對進程外對象的調用都是一致的,而不管它在怎樣的線程模型。以被調用者的角度來看,不管調用者的線程模型如何,所獲得的調用都是一致的。
客戶端和進程外對象之間的交互也很直接,即使它們使用了不同的線程模型,因為它們屬于不同的進程。COM介入了客戶端和服務器之間,通過標準的列集和RPC,并提供了跨線程操作的代碼。
4 第二方COM組件的調用者
4.1 各種調用
4.1.1 同一線程中的調用
同步問題:不需要,調用者和組件在同一線程中,自然同步。
調整問題:不需要,COM庫不需要任何介入,參數也不需要調整,組件也不必線程安全。
調用地點:當前線程
這是最簡單的情況。
4.1.2 套間線程之間的調用
同步問題:COM庫對調用進行同步。
調整問題:不管兩個套間是否在同一進程,都需要調整。某些情況下,需要手動調整。
調用地點: 對象所在套間線程。
4.1.3 自由線程之間的調用
同步問題:COM不進行同步,組件自己同步。
調整問題:同一進程不調整,不同進程要調整。
調用地點:客戶線程。
4.1.4 自由線程調用套間線程的組件
同步問題:COM庫對調用進行同步。
調整問題:不管兩個套間是否在同一進程,都需要調整。某些情況下,需要手動調整。
調用地點:套間線程
4.1.5 套間線程調用自由線程的組件
同步問題:COM不進行同步,組件自己同步。
調整問題:需要調整,同一進程,COM會優(yōu)化調整。
調用地點:客戶線程。
4.2 手工調整
如果通過COM方法,所有的參數都由COM庫進行調整。有時候需要程序員手工對接口指針進行列集marshal和散集unmarshal,那就是在跨越套間邊界,但沒有通過COM庫進行通信的時候。更明確的說,不通過COM接口函數,通過我們自己寫的函數跨套間傳遞接口指針的時候。
情況一:跨套間傳遞接口指針。
情況二:類廠在另外的套間中,創(chuàng)建類實例,并傳回給客戶端的接口指針。
列集函數:CoMarshalInterThreadInterfaceInStream
散集函數:CoGetInterfaceAndReleaseStream
5 第三方COM組件的編寫者
組件將在哪種類型的套間中執(zhí)行,是編寫者決定的。對于進程外組件,要調用CoInitializeEx并指定參數,以顯示確定套間類型。對于進程內的服務器來說,因為客戶端已經調用CoInitializeEx產生套間了,為了允許進程內的服務器可以控制它們的套間類型,COM允許每個組件有自己不同的線程模型,并記錄在注冊表中。
HKEY_CLASSES_ROOT/CLSID/.../InprocServer32 鍵值ThreadingModel
5.1 線程模型屬性
組件編寫者可以實現:同一個組件,既可以在STA中運行,也可以在MTA中運行,還可以在兩中環(huán)境中同時存在。可以說組件有一種屬性說明可以在哪種環(huán)境中生存,屬性名叫做“線程模型”(相當于“隱藏”)也未嘗不可。COM+里真正引入了這個屬性,并叫做ThreadModel。這個屬性可以有如下取值:
1. Main Thread Apartment
2. Single Thread Apartment (Apartment)
3. Free Thread Apartment (Free)
4. Any Apartment (Both)
5. Neutral Apartment (N/A)
5.2 對象在哪個套間創(chuàng)建
下表中第一列為套間種類,第一行為對象線程模型屬性。那么,結果就是在這樣的套間中創(chuàng)建這樣的組件,組件在什么地方。在必要的時候,會創(chuàng)建一個代理,就是表中的宿主。
未指定
Apartment
Free
Both
Neutral
單線程
(非主)
主STA
當前套間
MTA
當前套間
NA
單線程
(主線程)
當前套間
當前套間
MTA
當前套間
NA
多線程
主STA
宿主STA
MTA
MTA
NA
Neutral
單線程
主線程套間
宿主STA
(本線程)
MTA
NA
NA
Neutral
多線程
主線程套間
宿主STA
MTA
NA
NA
5.3 屬性的選擇
原則是根據組件的功能選擇:
如果組件做I/O,首選Free,因為可以相應其他客戶端調用。
如果組件和用戶交互,首選Apartment,保持消息依次調用。
COM+首選N/A。
如果沒有定義,COM庫默認為是Main Thread Apartment。
Apartment簡單,Free強大但要自己同步。
6 鳴謝
《COM技術內幕》《COM本質論》《深入解析ATL》
6.1 MSDN2008
在MSDN 2008中相關文檔的位置:
Win32和COM開發(fā)
-組件開發(fā)
-組件對象模型
-SDK文檔
-COM
-COM Fundamentals
-Guide-Processes, Threads, and Apartments
Win32和COM開發(fā)
-組件開發(fā)
-COM+
-SDK文檔
-COM+(組件服務)
-COM+開發(fā)瀏覽
-COM+ Contexts and Threading Models
6.2 COM線程模型
http://hi.baidu.com/zhangqiuxi/blog/item/ca7aa52b0311b4fbe6cd401e.html
6.3 理解 COM 套間
http://www.vckbase.com/document/viewdoc/?id=1597
6.4 泛說"COM線程模型"
http://blog.csdn.net/guogangj/archive/2007/09/06/1774280.aspx
6.5 附泛說一文
COM線程模型在COM相關的基礎知識中應該算是難點,難的原因可能有這些:
1、需要對COM其它基礎知識有較深的了解,因為這個論題幾乎涉及到了COM所有其它的基礎知識。
2、學習者得非常了解Win32本身的線程模型,因為在Windows中COM的線程模型在建立在Win32線程模型的基礎上的。
3、COM線程模型所引用的概念十分抽象,不好理解。
如果你還沒有掌握 1,2 所提到的知識點,你可以馬上找一些書籍,迅速補充這些知識,如果你已經掌握了這些知識,那就給你的想象力上點油,輕松點。
6.5.1 開始想象
術語
公寓(Apartment)有的譯文譯作"套間"。這個術語抽象的是COM對象的生存空間,你還真的可以想象成公寓,線程就是住在公寓里的人。
單線程公寓(Single-Threaded Apartment STA) 這種房間是供有錢人住的單人間,設備齊全,服務周到。
多線程公寓(Multithreaded Apartment MTA) 住在這種房間里的人條件就差多了,那么多人就擠在一個大房間里頭,可是他們自強不息。個個健壯得不得了。
然后思考
單線程公寓與多線程公寓的本質差別有哪些?
如果另一個人要和住在單線程公寓的人通信,不能直接去找他,哪怕你也住在高貴的單人間。但你可以打電話。提醒一下,電話每次只能同時與一個人說話(他們還沒有用到電話會議之類的服務)。住在多線程公寓的人他們的房間有個大窗子,如果住在單人間(單線程公寓)的人想與他們通信,來窗口說就行,而且這個窗子比你想的要大,可以同時讓很多人對話。同一房間里的人不用說了,他們可以直通話。
6.5.2 回到現實
術語
1、公寓,如果從來就不用考慮線程同步的問題,就用不著這個概念了,可是 COM 決定支持強大的多線程,于是引入了這個概念,公寓決定了線程與外界通信的方式。每一個與COM對象打交道的線程必須先決定要進入哪種公寓。
2、單線程公寓,這種公寓本身只能包含一個線程,通過調用CoInitialize(NULL)進入。它有著與窗口類似的運作方式,回想一下窗口的運行方式:消息泵不斷的從消息隊列提取消息,然后調度給相應的窗口函數。這樣做的好處是,窗口函數永遠不會重入,也就是說永遠不會有同步的問題。單線程公寓也用了同樣的運作方式(所以該公寓中的線程的主函數必須有一個消息循環(huán)):對該公寓中線程所擁有的COM對象的調用被隊列化,只有當一個調用結束后,另個調用才會開始。那么組件對象的代碼也是永遠不會重入。
3、多線程公寓,這種公寓可以包含任意多的線程(具體數目由操作系統(tǒng)決定)。一個進程里頭只能包含一個這種公寓,所有調用 CoInitializeEx(NULL, COINIT_MULTITHEADED)都會進入這個公寓。對該公寓中線程所擁有的COM對象的調用是直接的(先不考慮跨進程的情況),包括本公寓中的線程與其它的STA線程。
然后思考
單線程公寓與多線程公寓 的本質差別有哪些?
單線程公寓實現同步,有很多COM庫的干預,包括將外部的調用轉化成窗口消息,然后那個特別的隱藏窗口的窗口函數把窗口消息轉化成COM對象的函數調用。這樣的模型可以減小開發(fā)組件的難度,可是,卻犧牲了效率。多線程公寓把實現同步任務全部交給了組件自己,所以在這種公寓中生存的COM對象必須足夠健壯,考慮各種同步問題,不至于多個線程在調用對象的成員函數時會打架。
6.5.3 弄清它們的關系
弄清公寓,線程,對象的關系是很重要的,你弄清了嗎?如果你沒有弄清,那上面的這些也一定也是看得懵懵懂懂。公寓是這里面最大的單位,它是線程的容器。如果調用CoInitialize(0),COM庫會創(chuàng)建一個STA(注意,是"創(chuàng)建"),你的線程將屬于這個公寓,并且是這個公寓的唯一成員。如果 CoInitializeEx(NULL, COINIT_MULTITHEADED),而且是第一個要求進入MTA的線程,COM庫會創(chuàng)建一個MTA,其它后面調用 CoInitializeEx(NULL, COINIT_MULTITHEADED)的線程會直接進入(注意,我用的"進入")已有MTA。本來線程是一個運行的實體,不會分配資源,可是在 COM的線程模型里一個對象與創(chuàng)建它的線程是緊密相關的,稱對象歸屬于某個線程,至于這種歸屬關系是在COM庫內怎么管理,我們先不去管它,以后我們把線程A創(chuàng)建的對象說成是線程A的對象就行了(有一個例外,得說說,有一種Single 類型的COM對象,這種對象基實就是COM在提出線程模型前的產物,這種對象總是歸屬于主STA線程,即第一個調用CoInitialize(0)的線程。)
| 在這一部分我將講解COM提出的各個類型的線程模型,并說明COM運行時期庫是如何實現它們的。 本文講解COM提出的各個類型的線程模型,再說明COM運行時期庫是如何實現它們的. 線程模型是一種數學模型,專門針對多線程編程而提供的算法,但也僅是算法,不是實現。本文講解COM提出的各個類型的線程模型,再說明COM運行時期庫是如何實現它們的,就像說明Windows是如何實現線程這個數學模型的一樣,最后指明一下跨套間調用和各種類型套間編寫的要求以幫助理解。希望讀者對于Windows操作系統(tǒng)的線程這個概念相當熟悉,對何謂“線程安全的”亦非常了解。
套間實現規(guī)則
如果讀者對自己多線程編程的技術沒有信心,建議最好不要編寫可以存在于MTA套間的組件,不過就不能獲得MTA的高性能了。 在編寫MTA時還應該注意到線程親緣性(thread affinity)。沒有線程親緣性是指沒有任何線程范圍的成員變量,比如線程局部存儲(TLS)、窗口句柄等。也就是說在MTA中不能保存任何記錄著TLS內存的指針或窗口句柄,如果保存將沒有意義(比如A線程記錄的內存空間對B線程來說是無效的,因為TLS構造了一個線程相關的內存空間,就像每個進程都有自己的私有空間)。而不幸地MFC在它的底層運作機制的實現中大量使用了TLS,如模塊線程狀態(tài)、線程狀態(tài)等。正是由于這個原因,MFC不能編寫在MTA中運行的組件。 NA 由于可能會多個線程同時訪問NA套間的對象,因此和MTA一樣,其不能有線程親緣性并需要保護每個成員和全局及靜態(tài)變量。而關于NA的輕量級代理,是由COM+運行時期庫生成的,讀者完全不用操心(只需將那個組件的ThreadingModel鍵值賦值為“Neutral”即可)。 前面提到過有一種進程內組件的ThreadingModel鍵值可以被賦為“Both”,這種組件很像NA,哪個套間都可能直接訪問它,但只是可能,而NA組件是可以,這點可以從前面的那個進程內組件所屬套間的規(guī)則表中看出。這種組件可以支持一種稱作自由線程匯集器(FTM——Free Threaded Marshaler)的技術,由于其與本文題目無關,在此不表。當Both的組件使用了自由線程匯集器時,除了滿足MTA的要求以外(上面所說的線程安全保護和沒有線程相關性),還要記錄傳進來的接口指針的中立形式(比如IStream*,通過CoMarshallInterface得到),以防止對客戶的回調問題。 最后只是提醒一下,有3個STA套間,STA1、STA2和STA3。STA1用CoMarshallInterface得到的IStream*傳到STA2中通過CoUnmarshalInterface得到的代理和在STA3中同樣通過CoUnmarshalInterface得到的代理不同,不能混用。因為當STA2和STA3調用在STA1的對象時,STA1如果回調(連接點技術就是一種回調)調用者,則STA2和STA3的代理能分別正確的指出需要讓哪個線程執(zhí)行回調操作,即向哪個線程發(fā)送消息,因此不能混用。 |
總結
以上是生活随笔為你收集整理的[转]COM线程模型-套间的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Delphi利用MSCOMM控件进行GP
- 下一篇: 面对小点点谷歌广告表示很无奈