




已閱讀5頁,還剩4頁未讀, 繼續(xù)免費(fèi)閱讀
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
COM線程模型詳解COM中有下面一些使用規(guī)則。l 從一個線程調(diào)用Coinitliaze開始,到CoUninitliaze結(jié)束,這段區(qū)域稱為套間(appartment)。l COM對象接口必須在套間內(nèi)創(chuàng)建和使用。l 接口指針不能跨套間使用,也就是說,在A套間創(chuàng)建的接口指針不能在B套間使用。l A套間創(chuàng)建的接口指針可以Marshal-UnMarshal 在B套間生成一個新的接口指針,然后B 套間可以用它。l 跨套間有幾種情況:跨線程,跨進(jìn)程,遠(yuǎn)程,都可以用Marshal機(jī)制 抽象的統(tǒng)一處理。l Marshal 機(jī)制簡單的說是在當(dāng)前套間生成一個代理對象用來調(diào)用實(shí)際對象。線程模型是一種數(shù)學(xué)模型,專門針對多線程編程而提供的算法,但也僅是算法,不是實(shí)現(xiàn)。本文講解COM提出的各個類型的線程模型,再說明COM運(yùn)行時期庫是如何實(shí)現(xiàn)它們的,就像說明Windows是如何實(shí)現(xiàn)線程這個數(shù)學(xué)模型的一樣,最后指明一下跨套間調(diào)用和各種類型套間編寫的要求以幫助理解。希望讀者對于 Windows操作系統(tǒng)的線程這個概念相當(dāng)熟悉,對何謂“線程安全的”亦非常了解。COM線程模型COM提供的線程模型共有三種:Single-Threaded Apartment(STA 單線程套間)、Multithreaded Apartment(MTA 多線程套間)和Neutral Apartment/Thread Neutral Apartment/Neutral Threaded Apartment(NA/TNA/NTA 中立線程套間,由COM+提供)。雖然它們的名字都含有套間這個詞,這只是COM運(yùn)行時期庫(注意,不是COM規(guī)范,以下簡稱COM)使用套間技術(shù)來實(shí)現(xiàn)前面的三種線程模型,應(yīng)注意套間和線程模型不是同一個概念。COM提供的套間共有三種,分別一一對應(yīng)。而線程模型的存在就是線程規(guī)則的不同導(dǎo)致的,而所謂的線程規(guī)則就只有兩個:代碼是線程安全的或不安全的,即代碼訪問公共數(shù)據(jù)時會或不會發(fā)生訪問沖突。由于線程模型只是個模型,概念上的,因此可以違背它,不過就不能獲得COM提供的自動同步調(diào)用及兼容等好處了。STA 一個對象只能由一個線程訪問(通過對象的接口指針調(diào)用其方法),其他線程不得訪問這個對象,因此對于這個對象的所有調(diào)用都是同步了的,對象的狀態(tài)(也就是對象的成員變量的值)肯定是正確變化的,不會出現(xiàn)線程訪問沖突而導(dǎo)致對象狀態(tài)錯誤。其他線程要訪問這個對象,必須等待,直到那個唯一的線程空閑時才能調(diào)用對象。注意:這只是要求、希望、協(xié)議,實(shí)際是否做到是由COM決定的。如上所說,這個模型很像Windows提供的窗口消息運(yùn)行機(jī)制,因此這個線程模型非常適合于擁有界面的組件,像ActiveX控件、OLE文檔服務(wù)器等,都應(yīng)該使用STA的套間。MTA 一個對象可以被多個線程訪問,即這個對象的代碼在自己的方法中實(shí)現(xiàn)了線程保護(hù),保證可以正確改變自己的狀態(tài)。這對于作為業(yè)務(wù)邏輯組件或干后臺服務(wù)的組件非常適合。因?yàn)樽鳛橐粋€分布式的服務(wù)器,同一時間可能有幾千條服務(wù)請求到達(dá),如果排隊(duì)進(jìn)行調(diào)用,那么將是不能想像的。注意:這也只是一個要求、希望、協(xié)議而已。NA 一個對象可以被任何線程訪問,與MTA不同的是任何線程,而且當(dāng)跨套間訪問時(后面說明),它的調(diào)用費(fèi)用(耗費(fèi)的CPU時間及資源)要少得多。這準(zhǔn)確的說都已經(jīng)不能算是線程模型了,它是結(jié)合套間的具體實(shí)現(xiàn)而提出的要求,它和MTA不同的是COM的實(shí)現(xiàn)方式而已。COM套間Apartment被翻譯成套間或是單元,是線程模型的一個實(shí)現(xiàn)者,就像在操作系統(tǒng)課程中講到的線程只是一個數(shù)學(xué)模型,而Windows的線程、進(jìn)程是它(數(shù)學(xué)模型的線程、進(jìn)程)的實(shí)現(xiàn)者。套間只是邏輯上的一個概念,實(shí)現(xiàn)時只是一個結(jié)構(gòu)(由COM管理)而已,記錄著相關(guān)信息,如它的種類(只能是上面那三個,至少現(xiàn)在是),并由COM根據(jù)那個結(jié)構(gòu)進(jìn)行相應(yīng)的處理。下面說明這三種套間的實(shí)現(xiàn)方式:STA套間一個套間如果是STA,那么那個套間有且只有一個線程和其關(guān)聯(lián),有多個對象或沒有對象和其關(guān)聯(lián),就像有多個線程和一個進(jìn)程關(guān)聯(lián)一樣,也就是說套間那個結(jié)構(gòu)和某個線程及多個對象之間有關(guān)系,關(guān)系具體是什么由COM說得算,幸運(yùn)的是COM正是按照上面的線程模型來定義互相之間關(guān)系的。根據(jù)上面的算法,很容易就知道只有這個線程可以訪問這個套間里的對象。COM是通過在STA套間里的線程中創(chuàng)建一個隱藏窗口,然后外界(這個套間外的線程)對這個對象的調(diào)用都轉(zhuǎn)變成對那個隱藏窗口發(fā)送消息,然后由這個隱藏窗口的消息處理函數(shù)來實(shí)際調(diào)用組件對象的方法來實(shí)現(xiàn)STA的規(guī)則的。之所以使用一個隱藏窗口是為了方便組件代碼的編寫只需調(diào)用DispatchMessage即可將方法調(diào)用的消息和普通的消息區(qū)分開來(通過隱藏窗口的消息處理函數(shù))。外界對這個對象的調(diào)用都將轉(zhuǎn)變成對這個隱藏窗口的消息發(fā)送來實(shí)現(xiàn)同步。至于COM如何截獲外界對對象的調(diào)用,則是利于代理對象,后面再說明。值得注意的是,如果使用標(biāo)準(zhǔn)匯集法生成代理對象,則代理對象會根據(jù)是進(jìn)程內(nèi)還是進(jìn)程外的跨套間調(diào)用,來決定具體操作。如果外界線程和STA線程在同一進(jìn)程內(nèi),則代理對象將直接向STA線程中的隱藏窗口發(fā)送消息;如果不在同一進(jìn)程內(nèi)(包括遠(yuǎn)程進(jìn)程),代理對象將向RPC管理的一個線程池請求一個線程(RPC線程)來專門向另一進(jìn)程中的STA線程的隱藏窗口發(fā)送消息,而不是代理對象直接發(fā)送消息,以防止外界線程由于網(wǎng)絡(luò)等不穩(wěn)定因素而導(dǎo)致掛起。因?yàn)镃OM利用消息機(jī)制來實(shí)現(xiàn)STA,因此STA套間里的線程必須實(shí)現(xiàn)消息循環(huán),否則COM將不能實(shí)現(xiàn)STA的要求。MTA套間 這種類型的套間可以和多個線程及多個或沒有對象相關(guān)聯(lián)。根據(jù)上面的MTA模型,可知只有這個套間里的線程才能訪問這個套間里的對象,和STA不同的只是可以多個線程同時訪問對象。外界(不屬于這個套間的線程)對這個套間里的對象的調(diào)用將會導(dǎo)致調(diào)用線程(外界線程,也就是STA線程,因?yàn)镹A沒有線程)掛起,然后向RPC管理的一個線程池請求一個線程(RPC線程,并已經(jīng)進(jìn)入了這個MTA套間)以調(diào)用那個對象的方法。對象返回后,調(diào)用線程被喚醒,繼續(xù)運(yùn)行。雖然可以讓STA線程直接調(diào)用對象(而不用像前述的掛起等待另一個線程來調(diào)用對象),但這是必須的,因?yàn)榭赡軙谢卣{(diào)問題,比如這個MTA線程又反過來回調(diào)外界線程中的組件對象(假設(shè)客戶本身也是一個組件對象,這正是連接點(diǎn)技術(shù)),如果異步回調(diào)將可能發(fā)生錯誤。反過來,MTA的線程訪問STA里的對象時,COM將把調(diào)用轉(zhuǎn)換成對STA線程里那個隱藏窗口的一個消息發(fā)送,返回后再由COM轉(zhuǎn)成結(jié)果返回給MTA的線程(如果使用標(biāo)準(zhǔn)匯集法生成標(biāo)準(zhǔn)代理對象,則發(fā)生的具體情況就如上面STA套間所述)。因此STA和MTA都是只能由它們關(guān)聯(lián)的線程調(diào)用它們關(guān)聯(lián)的對象。而根據(jù)上面所說,當(dāng)MTA調(diào)STA或 STA調(diào)MTA,都會發(fā)生線程切換,也就是說一個線程掛起而換成執(zhí)行另一個線程。這是相當(dāng)大的消耗(需要從內(nèi)核模式向用戶模式轉(zhuǎn)換,再倒轉(zhuǎn)好幾回),而 NA就是針對這個設(shè)計(jì)的。NA套間 這種套間只和對象相關(guān)聯(lián),沒有關(guān)聯(lián)的線程,因此任何線程都可以直接訪問里面的對象,不存在STA的還是MTA的。外界(其實(shí)就是任何線程)對這個套間里面的調(diào)用都不需要掛起等待,而是進(jìn)入NA套間,直接調(diào)用對象的方法。NA套間是由COM+提供的,COM+中的每個對象都有一個環(huán)境和其相綁定,環(huán)境記錄了必要的信息,并監(jiān)聽對對象的每一次調(diào)用,以保證當(dāng)將對象的接口指針成員變量進(jìn)行傳遞或回調(diào)時其操作的正確性(保證執(zhí)行線程在正確的套間內(nèi),MTA線程就是通過將自己掛起以等待STA線程的消息處理完畢來保證的),從而避免了調(diào)用線程的掛起,因此這個代理(其實(shí)也就是環(huán)境的一部分)被稱作輕量級代理(相對于STA套間和MTA套間的重量級代理需要掛起調(diào)用線程,發(fā)生線程切換)。這個輕量級代理并不是永遠(yuǎn)都不發(fā)生線程切換。當(dāng)NA對象里有個對指向一個STA對象的指針的調(diào)用而調(diào)用線程不是那個STA對象關(guān)聯(lián)的線程時,調(diào)用將會轉(zhuǎn)成向被調(diào)用的 STA對象的關(guān)聯(lián)線程發(fā)送消息,此時照樣會發(fā)生線程切換。同理,如果那個對象是MTA的,而調(diào)用線程是STA線程時,依舊發(fā)生線程切換。不過除此以外的大多數(shù)情況(即不在NA對象的方法中調(diào)用另一個套間對象的方法)都不會發(fā)生線程切換,即使出現(xiàn)上面的情況也只有必要(MTA調(diào)NA再調(diào)MTA就不用切換)才切換線程。根據(jù)上面所說,STA其實(shí)和MTA邏輯上是完全一樣的,只是一個是關(guān)聯(lián)一個線程,一個是關(guān)聯(lián)多個線程而已。但把它們分開是必要的,因?yàn)榫€程安全就是針對是一個線程還是多個線程。而NA之所以不關(guān)聯(lián)線程是因?yàn)樗哪康氖窍厦婵缣组g調(diào)用時產(chǎn)生的線程切換損耗,關(guān)聯(lián)線程沒有任何意義。COM強(qiáng)行規(guī)定(不遵守也沒轍,因?yàn)槿荂OM實(shí)現(xiàn)套間的,根本沒有插手的余地)一個進(jìn)程可以擁有多個STA的套間,但只能擁有一個MTA套間和一個NA套間,我想這應(yīng)該已經(jīng)很容易理解了(要兩個MTA套間或NA套間干甚?)。套間生成規(guī)則線程在進(jìn)行大多數(shù)COM操作之前,需要先調(diào)用CoInitialize或 CoInitializeEx。調(diào)用CoInitialize告訴COM生成一個STA套間,并將當(dāng)前的調(diào)用線程和這個套間相關(guān)聯(lián)。而調(diào)用 CoInitializeEx( NULL, COINIT_MULTITHREADED );告訴COM檢查是否已經(jīng)有了一個MTA套間,沒有則生成一個MTA套間,然后將那個套間和調(diào)用線程相關(guān)聯(lián)。接著在調(diào)用 CoCreateInstance或CoGetClassObject等創(chuàng)建對象的函數(shù)時,創(chuàng)建的對象將以一個特定規(guī)則決定和哪個套間相關(guān)聯(lián)(后敘)。這樣完成后,就完成了線程、對象和套間的關(guān)聯(lián)(或綁定)。前面提到的決定對象去向的規(guī)則如下。當(dāng)是進(jìn)程內(nèi)組件時,根據(jù)注冊表項(xiàng)InprocServer32ThreadingModel和線程的不同,列于下表:創(chuàng)建線程關(guān)聯(lián)的套間種類ThreadingModel鍵值組件對象最后所在套間 STAApartment創(chuàng)建線程的套間STAFree進(jìn)程內(nèi)的MTA套間STABoth創(chuàng)建線程的套間 STA或Single進(jìn)程內(nèi)的主STA套間 STA Neutral進(jìn)程內(nèi)的NA套間 MTAApartment新建的一個STA套間 MTAFree 進(jìn)程內(nèi)的MTA套間MTABoth進(jìn)程內(nèi)的MTA套間 MTA 或Single 進(jìn)程內(nèi)的主STA套間 MTANeutral進(jìn)程內(nèi)的NA套間進(jìn)程內(nèi)的主STA套間是進(jìn)程中第一個調(diào)用CoInitialize的線程所關(guān)聯(lián)的套間(即進(jìn)程中的第一個STA套間)。后面說明為什么還來個進(jìn)程內(nèi)的主STA套間。當(dāng)是進(jìn)程外組件時,由主函數(shù)調(diào)用CoInitializeEx或CoInitialize指定組件所在套間,與上面的相同,CoInitialize代表STA,CoInitializeEx( NULL, COINIT_MULTITHREADED );代表MTA,沒有NA。因?yàn)镹A是COM+提供的,而COM+服務(wù)只能提供給進(jìn)程內(nèi)服務(wù)器,因此只使用上面的注冊表項(xiàng)的規(guī)則決定DLL組件是否放進(jìn) NA套間,而沒有提供類似CoInitializeEx( NULL, COINIT_NEUTRAL );來處理EXE組件。而且如果可以使用CoInitializeEx( NULL, COINIT_NEUTRAL );將導(dǎo)致調(diào)用線程和NA套間相關(guān)聯(lián)了,違背了NA的線程模型,這也是為什么ThreadingModel鍵在InprocServer32鍵下。跨套間調(diào)用STA線程1創(chuàng)建了一個STA對象,得到接口指針I(yè)ABCD*,接著它發(fā)起STA線程2,并且將IABCD*作為線程參數(shù)傳入。在線程2中,調(diào)用 IABCD:Abc()方法,成功或者失敗天注定。由于線程2所在的STA套間不同于線程1所在的STA套間,這樣線程2就跨套間調(diào)用另一個套間的對象了。按照前述的STA規(guī)則,IABCD:Abc()應(yīng)該被轉(zhuǎn)成消息來發(fā)送,而如果如上面做法,可以,編譯通過,不過運(yùn)行就不保證了。COM之所以能夠?qū)崿F(xiàn)前面所說的那些規(guī)則(STA、MTA、NA),是因?yàn)榭缣组g調(diào)用時,被調(diào)用的對象指針是指向一個代理對象,不是組件對象本身。而那個代理對象實(shí)現(xiàn)前述的那三個實(shí)現(xiàn)算法(轉(zhuǎn)成消息發(fā)送,線程切換等),而一般所說的代理/占位對象(Proxy/Stub)等其實(shí)都只是指進(jìn)行匯集工作的代碼(后述)。而按照上面直接通過線程參數(shù)傳入的指針是直接指向?qū)ο蟮?,所以將不能?shí)現(xiàn)STA規(guī)則,為此COM提供了如下兩個函數(shù)(還有其他方式,如通過全局接口表GIT)來方便產(chǎn)生代理:CoMarshalInterface和CoUnmarshalInterface(如果在同一進(jìn)程內(nèi)的線程間傳遞接口指針,則可以通過這兩個函數(shù)來進(jìn)一步簡化代碼的編寫:CoMarshalInterThreadInterfaceInStream和 CoGetInterfaceAndReleaseStream)?,F(xiàn)在重寫上面代碼,線程1得到IABCD*后,調(diào)用 CoMarshalInterface得到一個IStream*,然后將IStream*傳入線程2,在線程2中,調(diào)用 CoUnmarshalInterface得到IABCD*,現(xiàn)在這個IABCD*就是指向代理對象的,而不是組件對象了。因此,前面所說過的所有線程模型的算法都是通過代理對象實(shí)現(xiàn)的。要跨套間時,使用CoMarshalInterface將代理對象的CLSID和其與組件對象建立聯(lián)系的一些必要信息(如組件對象的接口指針)列集(Marshaling)到一個IStream*中,再通過任何線程間通信手段(如全局變量等)將 IStream*傳到要使用的線程中,再用CoUnmarshalInterface散集(Unmarshaling)出接口以獲得指向代理對象的接口指針。因此之所以要獲得代理對象的指針是因?yàn)橄胧褂肅OM提供的線程模型(但在COM+中,這不是唯一的理由),如果不想使用大可不必這么麻煩(不過后果自負(fù)),并沒有強(qiáng)制要求必須那么做。當(dāng)線程1和線程2都是MTA時,則可以像最開始說的那樣,直接傳遞IABCD*到線程2中,因?yàn)?MTA線程模型同意多個線程同時直接調(diào)用對象,線程1和線程2在同一個MTA套間中,而那個對象通過某種形式(如ThreadingModel = Free)向COM聲明了自己支持MTA線程模型。而當(dāng)a.exe的線程1和b.exe的線程2都是MTA時,則依舊需要像上面那樣進(jìn)行接口指針的匯集(列集傳輸散集這個過程)以得到指向代理而非對象的指針,即使線程1和線程2都是在MTA套間中,卻是在兩個不同的MTA套間中,因此是跨套間調(diào)用,需要匯集操作。匯集代碼前面已經(jīng)說明了套間的規(guī)則都是通過對代理對象而非組件對象發(fā)起調(diào)用以截取對組件對象的調(diào)用由代理對象來實(shí)現(xiàn)的。代理對象要和組件對象交互,將方法參數(shù)傳遞給組件對象,需要使用到匯集技術(shù),也就是列集傳輸散集這個過程。列集(Marshaling)指將信息以某種格式存為流(IStream*)形式的操作;散集(Unmarshaling)則是列集的反操作,將信息從流形式中反還出來;傳輸則只是流形式的傳遞操作。這里經(jīng)常發(fā)生誤會。前面的CoMarshalInterface所做的列集,是將代理對象的CLSID及一些持久信息(用于初始化代理對象)格式化為一種格式(網(wǎng)絡(luò)數(shù)據(jù)描述Network Data Representation)后放到一個流對象中,可以通過網(wǎng)絡(luò)(或其他方式)將這個流對象傳遞到客戶機(jī),由客戶通過 CoUnmarshalInterface從傳來的流對象中反還出代理對象的CLSID和初始化用的一些持久信息,生成代理對象并使用持久信息初始化它以用于匯集操作。這就是發(fā)生誤會的地方這里的匯集操作不同于上面的匯集操作,其匯集的是接口方法的參數(shù)而不是什么CLSID和一些初始化信息。因此CoMarshalInterface和CoUnmarshalInterface是用于匯集接口指針的,再準(zhǔn)確點(diǎn)應(yīng)該是用于生成代理對象的。代理對象應(yīng)由讀者自己實(shí)現(xiàn),用于匯集接口方法的參數(shù)。一般有兩種代理對象的實(shí)現(xiàn)方式:自定義匯集和標(biāo)準(zhǔn)匯集。對于自定義匯集,組件需實(shí)現(xiàn)IMarshal接口和一個代理組件(即完全實(shí)現(xiàn)真正組件所有接口的一個副本,實(shí)現(xiàn)了匯集方法參數(shù)及線程模型的規(guī)則,也必須實(shí)現(xiàn)IMarshal接口),并將這個代理組件在客戶機(jī)上注冊,以保證代理對象的正確生成。注意:如果參數(shù)中有接口指針,必須用 CoMarshalInterface和CoUnmarshalInterface進(jìn)行匯集,否則無法實(shí)現(xiàn)正確的線程模型,且代理組件是線程模型的實(shí)現(xiàn)者,這點(diǎn)組件必須自己保證(如發(fā)送消息等)。對于標(biāo)準(zhǔn)匯集,組件無需實(shí)現(xiàn)IMarshal接口及代理組件,代替的,組件則需要為自己生成一個代理/占位組件(Proxy/Stub),其由于可通過MIDL由IDL文件自動生成,效率高,代碼的正確性有保證,因而被鼓勵使用。COM提供了一個標(biāo)準(zhǔn)代理對象的實(shí)現(xiàn),其通過聚合組件的代理/占位組件以表現(xiàn)出其好像是組件的代理對象。與自定義匯集一樣,需要將這個代理/占位組件在客戶機(jī)上注冊以保證代理對象的正確生成。至于這兩種匯集的具體工作機(jī)理,由于與本文無關(guān),在此不表,這里僅僅只為消除代理對象和代理/占位組件之間的混淆。注意:對于將運(yùn)行于NA套間的組件,由于COM+的強(qiáng)制要求,其必須使用標(biāo)準(zhǔn)匯集進(jìn)行代理對象的生成而不是自定義匯集(COM+運(yùn)行時期庫重寫了標(biāo)準(zhǔn)代理對象來截獲對組件對象的調(diào)用和其自身的某些特殊處理如保證NA套間正確工作)。套間實(shí)現(xiàn)規(guī)則如前面所說,COM的套間機(jī)制要成功,必須服務(wù)器(組件)、客戶和COM運(yùn)行時期庫三方面合力實(shí)現(xiàn),其中有任何一方不按著規(guī)矩來,將不能實(shí)現(xiàn)套間機(jī)制的功能,不過這并不代表什么錯誤,套間機(jī)制不能運(yùn)作并不代表程序會崩潰,只是不能和其他COM應(yīng)用兼容而已。比如:對象中的屬性1在設(shè)計(jì)的算法中肯定不會被兩個以上的線程寫入,只是會被多個線程同時讀出而已,因此不用同步,可以用MTA,但對象的屬性2卻可能被多個線程寫入,因此決定使用STA。從而在客戶端,通過前面說的CoMarshalInterface和CoUnmarshalInterface將對象指針傳到那個只會寫入對象的屬性1的線程,其實(shí)這時就可以直接將對象指針傳到這個線程,而不用想上面那樣麻煩(而且增加了效率),但是就破壞了COM的套間規(guī)矩了兩個線程可以訪問對象,但對象在STA套間中。所以?!什么事都不會發(fā)生,因?yàn)橐呀?jīng)準(zhǔn)確知道這個算法不會捅婁子(線程訪問沖突),即使破壞COM的規(guī)矩又怎樣?!而且組件仍可以和其他客戶兼容,因?yàn)椴话匆?guī)矩來的是客戶,與組件無關(guān)。不過如果組件破壞規(guī)矩,那么它將不能和每一個客戶兼容,但并不代表它和任何客戶都不兼容。這里其實(shí)就是客戶和組件聯(lián)合起來欺騙了COM運(yùn)行時期庫。上面的例子只是想幫助讀者加深對套間的理解,實(shí)際中應(yīng)該盡量保持和COM規(guī)范的兼容性(但不兼容并不代表是錯誤的)??蛻粢龅墓ぷ髑懊嬉呀?jīng)說過了(那兩個函數(shù)或全局接口表或其他只要正確的方式),下面說明組件應(yīng)該做的工作。組件可以存在于四個套間中(多了一個主STA套間),所需工作分別如下:STA 當(dāng)一個組件是STA時,它必須同步保護(hù)全局變量和靜態(tài)變量,即對全局變量和靜態(tài)變量的訪問應(yīng)該用臨界段或其他同步手段保護(hù),因?yàn)椴僮魅趾挽o態(tài)變量的代碼可以被多個STA線程同時執(zhí)行,所以那些代碼的地方要進(jìn)行保護(hù)。比如對象計(jì)數(shù)(注意,不是引用計(jì)數(shù)),代表當(dāng)前組件生成的對象個數(shù),當(dāng)減為零時,組件被卸載。此變量一般被類廠對象使用,還好ATL和MFC已經(jīng)幫我們實(shí)現(xiàn)了缺省類廠,這里一般不用擔(dān)心,但自定義的全局或靜態(tài)變量得自己處理。主STA 與STA唯一的不同是這是傻瓜型的,連靜態(tài)和全局變量都可以不用線程保護(hù),因?yàn)樗胁皇前踩L問靜態(tài)和全局變量的對象都通過主線程(第一個調(diào)用 CoInitialize的線程)的消息派送機(jī)制運(yùn)行,因此不安全的訪問都被集中到了一個線程的調(diào)用中,因而調(diào)用被序列化了,也就實(shí)現(xiàn)了對靜態(tài)和全局變量的線程保護(hù)。至于為什么是主線程,因?yàn)檫M(jìn)程要使用STA,則一定會創(chuàng)建主線程,所以一定可以創(chuàng)建主STA。因此主STA并不是什么第四種套間,只是一個 STA套間,不過關(guān)聯(lián)的是主線程而已,由于它可以被用作保護(hù)靜態(tài)和全局變量而被單獨(dú)提出來說明。因此一個進(jìn)程內(nèi)也只有一個主STA套間。MTA 必須對組件中的每個成員和全局及靜態(tài)變量的訪問使用同步手段進(jìn)行保護(hù),還應(yīng)考慮線程問題,即不是簡單地保護(hù)訪問即可,還應(yīng)注意線程導(dǎo)致的錯誤的操作,最經(jīng)典的就是IUnknown:Release()。DWORD IUnknown:Release()DWORD temp = InterlockedDecreament( &m_RefCount );if( !temp ) / 不能用m_RefCount,原因請自己思考delete this; / 因此不是只要用原子訪問函數(shù)保護(hù)了m_RefCount的訪問就行了return temp; / 前面對全局變量的保護(hù)也和此類似,要考慮線程問題如果讀者對自己多線程編程的技術(shù)沒有信心,建議最好不要編寫可以存在于MTA套間的組件,不過就不能獲得MTA的高性能了。在編寫MTA時還應(yīng)該注意到線程親緣性(thread affinity)。沒有線程親緣性是指沒有任何線程范圍的成員變量,比如線程局部存儲(TLS)、窗口句柄等。也就是說在MTA中不能保存任何記錄著 TLS內(nèi)存的指針或窗口句柄,如果保存將沒有意義(比如A線程記錄的內(nèi)存空間對B線程來說是無效的,因?yàn)門LS構(gòu)造了一個線程相關(guān)的內(nèi)存空間,就像每個進(jìn)程都有自己的私有空間)。而不幸地MFC在它的底層運(yùn)作機(jī)制的實(shí)現(xiàn)中大量使用了TLS,如模塊線程狀態(tài)、線程狀態(tài)等。正是由于這個原因,MFC不能編寫在 MTA中運(yùn)行的組件。NA 由于可能會多個線程同時訪問NA套間的對象,因此和MTA一樣,其不能有線程親緣性并需要保護(hù)每個成員和全局及靜態(tài)變量。而關(guān)于NA的輕量級代理,是由 COM+運(yùn)行時期庫生成的,讀者完全不用操心(只需將那個組件的ThreadingModel鍵值賦值為“Neutral”即可)。前面提到過有一種進(jìn)程內(nèi)組件的ThreadingModel鍵值可以被賦為“Both”,這種組件很像NA,哪個套間都可能直接訪問它,但只是可能,而 NA組件是可以,這點(diǎn)可以從前面的那個進(jìn)程內(nèi)組件所屬套間的規(guī)則表中看出。這種組件可以支持一種稱作自由線程匯集器(FTMFree Threaded Marshaler)的技術(shù),由于其與本文題目無關(guān),在此不表。當(dāng)Both的組件使用了自由線程匯集器時,除了滿足MTA的要求以外(上面所說的線程安全保護(hù)和沒有線程相關(guān)性),還要記錄傳進(jìn)來的接口指針的中立形式(比如IStream*,通過CoMarshallInterface得到),以防止對客戶的回調(diào)問題。最后只是提醒一下,有3個STA套間,STA1、STA2和STA3。STA1用 CoMarshallInterface得到的IStream*傳到STA2中通過CoUnmarshalInterface得到的代理和在STA3中同樣通過CoUnmarshalInterface得到的代理不同,不能混用。因?yàn)楫?dāng)STA2和STA3調(diào)用在STA1的對象時,STA1如果回調(diào)(連接點(diǎn)技術(shù)就是一種回調(diào))調(diào)用者,則STA2和STA3的代理能分別正確的指出需要讓哪個線程執(zhí)行回調(diào)操作,即向哪個線程發(fā)送消息,因此不能混用。COM的多線程模型是COM技術(shù)里頭最難以理解的部分之一,很多書都有涉及但是都沒有很好的講清楚。很多新人都會在這里覺得很迷惑,google大神能搜到一篇vckbase上的文章,但是個人建議還是不要看的好幾乎是胡說八道在亂搞。COM自己其實(shí)并沒有任何多線程模型,所以他用的多線程模型還是WIN32里頭的那一套線程和同步對象。作為準(zhǔn)備,這里先簡單講一下WIN32的線程和同步。作為慣例一講WIN32的線程和同步對象就要把進(jìn)程、線程這兩個東西講一遍,但是這里不講,因?yàn)闀碈OM的對這部分已經(jīng)很熟悉了,如果不熟悉的話建議也不要看COM了先回頭看看Windows核心編程和Windows高級編程。WIN32的線程可以分為兩種,UI線程和工作線程。UI線程是一種與一個窗口綁定的線程,其特點(diǎn)是包含一個窗口一個消息循環(huán)和一個窗口過程,由于消息循環(huán)的存在導(dǎo)致了其天生就具有一種同步機(jī)制:任何發(fā)送到該線程的消息都會被消息循環(huán)同步,不會有任何兩個或以上的消息同時被窗口過程處理,所有消息都會被消息循環(huán)串行化;工作線程則可以認(rèn)為是一個函數(shù)在一個線程上的一次運(yùn)行,這種線程不具備任何自帶的同步機(jī)制,如果要對兩個工作者線程實(shí)施某種同步則只能使用WIN32的同步對象如CriticalSection或者 Event等等。接下來看COM的多線程模型,從VS2005的ATL工程向?qū)峡梢钥吹紺OM多線程模型分為這么幾類:單線程(Single)、套間 (Apartment)、兩者(Both)、自由(Free)。這個部分個人覺得翻譯不是很好,單線程(Single)個人認(rèn)為翻譯成單套間會比較好,原因后面有具體描述,但是作為尊重MS向?qū)Щ蛘卟恢劣诟影堰@部分弄得混亂,下面的術(shù)語還是引用MS向?qū)系闹v法并且我盡可能使用英文術(shù)語??梢钥吹紺OM多線程模型里最多用到的兩個字是套間,那么先解釋一下套間。套間可以根據(jù)他的英文想象一個房間,這個房間周圍有墻,所以要進(jìn)到這個房間必須用一種手段來穿透(通過門或者類似的東西),而這個房間里放的就是一個或者多個COM對象。對套間更理論性的解釋是,在一個套間內(nèi)存在一個或多個COM組件,而套間之間存在有一個明確的界限,并且套間內(nèi)只存在唯一的一個套間線程,這個套間線程存在一個類似于消息循環(huán)(其實(shí)不應(yīng)該用類似的,他就是一個隱藏了窗口的消息循環(huán))來保證其天生所具有的同步性??戳诉@個定義你會覺得套間像什么?沒錯,一個只有一個主線程的Windows窗口應(yīng)用程序進(jìn)程。所以套間就是一個UI線程!做為UI線程他自然就能完成同步的功能。接下去分幾個部分來講這幾種COM線程模型。一、單線程(Single)前面講過這個模型最好是被翻譯成單套間的好,因?yàn)檫@種多線程模型并不是說COM組件只能被用在單線程程序里頭的,相反組件還是可以被正常的用在多線程程序里的。這種模型的真實(shí)意義是即使你的程序是多線程的并且在每個線程里都調(diào)用了CoInitalize(0,COINIT_APARTMENT),事實(shí)上在你的程序進(jìn)程里頭也只創(chuàng)建一個套間,并且把所有的組件都放到這個套間里頭并由這個套間所擁有的消息循環(huán)來保證同步性。 或許這樣講不全面,但是上面一段確實(shí)講了一種最簡單的情況,就是所有的組件都按Single模型來創(chuàng)建。如果不是這樣會什么情況呢,舉個例子說A、B、C 三個組件按Single模型創(chuàng)建,D按Apartment模型創(chuàng)建,并且四個組件分別在TA、TB、TC、TD四個線程里創(chuàng)建實(shí)例(每個線程都調(diào)用 CoInitalize(0,COINIT_APARTMENT)來創(chuàng)建環(huán)境),那么組件A、B、C運(yùn)行在由TA創(chuàng)建的套間里(TB、TC都沒有創(chuàng)建套間,TA是這個套間的套間線程),而組件D則獨(dú)立運(yùn)行在TD創(chuàng)建的套間里(TD是這個套間的套間線程),這里一共就有了兩個套間。這樣應(yīng)該是完整的情況了,再復(fù)雜的情況我想你都能推出來了。二、套間(Apartment)這種模型與前一種模型很相似,可以都被認(rèn)為是創(chuàng)建WIN32概念上的UI線程,但是不同的在于,Single模型無論你在多少個線程里調(diào)用多少次 CoInitalize(0,COINIT_APARTMENT)都只創(chuàng)建一個套間,套間的套間線程是你第一次調(diào)用 CoInitalize(0,COINIT_APARTMENT)的線程,而Apartment模型則是你在一個線程上調(diào)用一個 CoInitalize(0,COINIT_APARTMENT)就創(chuàng)建一個套間,并且把這個線程作為套間線程。三、自由(Free)這種模型就是工作者線程了,COM不再用消息循環(huán)來提供同步機(jī)制了。你要在多線程里使用,OK,那你自己給他做同步機(jī)制(或者由組件開發(fā)者把組件做成線程安全的)。你要單線程里使用,那更好無論如何都不需要同步了。四、兩者(Both) 這種模型保證了組件即能在套間模型使用也能在自由模型使用。例如說組件自身被創(chuàng)建為Apartment或者Single,但是使用者用 CoInitalize(0,COINIT_MULITITHREAD)來創(chuàng)建環(huán)境,那么COM自己會再創(chuàng)建一個線程用 CoInitalize(0,COINIT_APARTMENT)來創(chuàng)建環(huán)境供組件運(yùn)行,反之亦然。 最后講一下跨套間調(diào)用的問題,從上面的描述可以看到在Single和Apartment這兩種模型里頭套間內(nèi)調(diào)用或者通過COM機(jī)制套間之間的通信都會被同步化,但是如何跨套間調(diào)用呢,比如說我們經(jīng)常做這種事情,把一個存在在套間內(nèi)組件的接口指針作為線程參數(shù)傳遞到另外一個線程中使用,或者兩個組件存在于不同的套間中,但是由于連接點(diǎn)或者回調(diào)的原因需要互相調(diào)用。這個時候我們就需要使用proxy/stub機(jī)制,在傳遞接口指針之前調(diào)用 CoMarshalInterThreadInterfaceInStream API來包裝接口指針給PS dll來傳遞,然后再那個調(diào)用的線程或者套間里使用CoGetInterfaceAndReleaseStream來重新獲得被調(diào)度過的接口指針,這樣就能確保COM的線程同步機(jī)制能夠正常的運(yùn)行。 好了,全部講完了,如果還不清楚建議去看一下COM技術(shù)內(nèi)幕里的第十二章,這本書是我見過的所有書里對這部分描述得最好的一本(勝過ATL技術(shù)內(nèi)幕),特別是里面那幾張圖,對理解這些模型非常有幫助,這書網(wǎng)上有電子版。MSGQ和COM線程模型COM的本質(zhì)是C/S,考慮線程安全的時候,也可以分別從Client和Server的角度考慮。從Client角度看,是如何安全的調(diào)用共享的Server;反之從Server角度看,是如何安全的對多線程的調(diào)用提供服務(wù)。以C+的角度看,Server就是一個C+類的實(shí)例,COM不過是很多機(jī)制幫助Client如何創(chuàng)建In-Process/Out Process的合適的C+實(shí)例。并在Out-Process的情況下,提供合適的Proxy/Stub來完成inter-process的調(diào)用。Proxy/Stub的實(shí)質(zhì)是RPC/IPC,具體實(shí)現(xiàn)上則依賴于Windows 的MessageQ。之所以依賴于MSGQ,是因?yàn)镸SGQ獨(dú)特而齷齪的非標(biāo)準(zhǔn)化設(shè)計(jì)。在Linux/Unix系統(tǒng)上,當(dāng)要等待多個外部事件,就是去select多個對應(yīng)的fd。Windows也提供select函數(shù)的支持,但是Windows MSGQ并不是一個可以被select的fd可以代替的,至少微軟公開的API不容許這樣的
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年軟考設(shè)計(jì)師綜合復(fù)習(xí)試題及答案
- 計(jì)算機(jī)軟件考試技巧及試題答案
- 農(nóng)村信用社員工工作總結(jié)模版
- 項(xiàng)目技術(shù)文檔撰寫要求試題及答案
- 風(fēng)險(xiǎn)管理的方法與工具試題及答案
- 2025年VB數(shù)據(jù)庫的CRUD操作考察題及答案
- 編程語言類型與特點(diǎn)試題及答案
- 行政法上基本權(quán)利保障試題與答案
- 2025年信息技術(shù)員考試關(guān)鍵試題及答案
- 法學(xué)概論熱點(diǎn)話題及試題答案
- 學(xué)?!靶@餐”專項(xiàng)整治推進(jìn)工作情況匯報(bào)范文
- 2024年撫順市三支一扶考試真題
- 道德與法治教育資源整合與利用方案
- 《WEBGIS編程入門教程》課件
- 2024年合肥濱湖投資控股集團(tuán)有限公司招聘真題
- 醫(yī)保基金管理專項(xiàng)整治部署
- 2024年濟(jì)南市工程咨詢院招聘考試真題
- 小兒推拿培訓(xùn)合同協(xié)議
- 防塵防潮倉庫管理制度
- 酒店房價(jià)體系管理制度
- 2025至2030年中國內(nèi)脫模劑數(shù)據(jù)監(jiān)測研究報(bào)告
評論
0/150
提交評論