在COM组件接口分为中能够创建工作线程吗

原文: 调用任何COM组件之前,你必须首先初始化COM套件环境,即调用CoInitialize或CoInitializeEx。COM套件环境在线程的生存周期内有效,线程退出前需要调用CoUninitialize释放COM套件。 所谓COM套件,实际上是微软为了方便大家理解而起的一个名字,不过个人认为改名词很难理解。COM套件只指COM组件运行时的环境,其中包括COM组件的数据、变量、线程调度方式。 COM套件分为两种模式,单线程套件(STA)和多线程套件(MTA)。不要单从字面上理解,例如:STA并非只能用于单线程的程序,多线程程序依然可以使用。下面列出两种套件模式的区别。套件类型说明性能兼容性常见错误STA单线程套间,一个进程内所有COM组件都运行在主STA中,主STA就是第一个调用CoInitialize函数的线程。也就是说,即使你拥有多线程程序,但在不通线程同时操作COM组件的时候,COM组件会通过Windows消息、Event同步对象之类的机制把调用转换到主STA执行,而主STA通常对应应用程序的主线程。这样对STA套件内的任何COM组件操作,实际上是单线程操作,COM组件不必关心线程同步的细节,因为根本没有必要进行线程同步。低如果一个COM组件是MTA的,可以安全的运行与STA套件中。 由于STA套件所有的COM组件代码都运行于主STA(第一个调用CoInitialize函数的线程),如果你的主线程没有调用CoInitialize,那么第一个调用CoInitialize的工作线程就会成为主STA,而工作线程随时可能中止,这种情况下,一旦工作线程中止主STA也就不复存在了,因此你需要在主线程中调用CoInitialize初始化主STA,即使主线程不使用任何COM组件。MTA多线程套间,所有COM组件都运行在本线程的MTA套件中。这是就会出现多个线程同时执行某个COM调用,COM组件的开发者必须预料并处理这种并发访问带来的内存竞争读写混乱。COM组件开发者通常会应用临界区、互斥量、信号灯之类的常规线程同步方法。而调用者,不需要担心COM组件是否会因为多线程挂掉。高 如果一个COM组件是STA的,被错误的运行与MTA套件会引发各种奇怪的错误。把STA的COM组件运行于MTA套件中会引发错误。 这就会引出一个问题,到底我该使用STA还是MTA呢,答案很简单问该COM组件的开发者,或者看他的说明文档,他们会告诉你。 STA套件的初始化方式(两种方式等效):1,CoInitialize(nil);2,CoInitializeEx(nil,COINIT_APARTMENTTHREADED); MTA套间的初始化方式:1,CoInitializeEx(nil,COINIT_MULTITHREADED); 附上一个多线程中,工作线程使用STA套件,但是主线程没有初始化主STA,引发的怪异错误的反面教材代码。01program STAT 02
04{$APPTYPE CONSOLE}05
Windows, 08
Variants, 09
ComObj, 10
Classes, 11
SysUtils, 12
ActiveX; 13
TScriptThread=class(TThread) 16
procedure E 18
ThreadTotal:Integer; 23
ThreadCount:Integer; 24
25procedure TScriptThread.E 26var FVBScriptEngine:V 27begin28
CoInitialize(nil); 32
FVBScriptEngine:=CreateOleObject('ScriptControl'); 34
FVBScriptEngine.AllowUI:=False; 35
FVBScriptEngine.Timeout:=500; 36
FVBScriptEngine.Language:='VBScript'; 37
InterlockedDecrement(ThreadCount); 39
on E:Exception do43
Writeln(E.Message); 44
end; 45end; 46
ThreadCount:=0; 59
ThreadTotal:=0; 60
while True do begin62
while ThreadCount&=100 do; 65
Inc(ThreadTotal); 66
With TScriptThread.Create(True) do begin67
InterlockedIncrement(ThreadCount); 68
FreeOnTerminate:=True; 69
Writeln(ThreadTotal); 72
on E:Exception do75
Writeln(E.Message); 76
Readln(Input); 78end.
COM组件初始化
OleInitialize、CoInitialize、CoInitializeEx和AfxOleInit()区别
( 22:49:25)
COM 初始化
COM 初始化,说简单很简单,说复杂,有些时候还真不简单。
首先,CoInitialize(NULL)和CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);是...
COM单线程套间详解
COM单线程套间详解
高级COM工程项目经常需要跨线程传递对象,以在不同线程中调这些对象方法,激发它们的事件。下面这篇文章针对具有基本的com知识(比如理解IUnkown和IDispa...
遇到的问题-----------c#在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式
今天在一个复杂的项目中添加一个弹出新窗口点击按钮后选择文件读取文件的功能。
在同一个命名空间Api中:
使用COM组件时,通常的做法是在main函数的最前面调用CoInitialize(NULL);,最后面调用CoUninitialize();
今天遇到一个问题是在线程中使用COM组件失败了,我就怀疑...
核心提示:MSDN上CoInitialize的解释:Initializes the COM library on the current apartment and identifies the co...
最近在网上发现c++builder中提供的CreateOleObject类对于com组件的调用十分实用,下边是调用iTrusPTA.dll的一个示例;CoInitialize(NULL);
void Cdirectshow_1Dlg::OnBnClickedButton1()
HRESULT err = ::CoInitialize(NULL);
if (FAILED...
CoInitialize(LPVOID),它将以特定参数调用CoInitializeEx,为当前单元初始化COM库,并标记协同模式为单线程模式。参数必须为NULL。这是关于OLE和COM的问题。
没有更多推荐了,COM多线程原理和应用
COM多线程一直是个不容易弄清的问题,我也被困扰了很久,特别是COM在线程方面的术语总是不能统一。本文是为了将我所学所用得做一个总结,本文不保证一定正确,但是会随着时间的推移逐渐完善改正。...
COM---多线程
将函数调用的参数从一个进程的地址空间传到另一个进程的地址空间。COM直接使用了Win32线程,但仍有细微差异。Win32线程:用户界面线程、工作线程
COM线程:套间线程(类似于用户界面线程...
COM的多线程模型
COM的多线程模型是COM技术里头最难以理解的部分之一,很多书都有涉及但是都没有很好的讲清楚。很多新人都会在这里觉得很迷惑,google大神能搜到一篇vckbase上的文章,但是个人建议还...
多线程中使用COM组件时的初始化问题
CoInitialize、CoInitializeEx都是windows的API,主要是告诉windows以什么方式为程序创建COM对象,原因是程序调用com库函数(除CoGetMalloc和内存分配...
vb使用CreateThread使用&em&多线程&/em&时,很容易出错,找规律好像是创建的线程执行工程内代码时引起的,为了避免这种状况的出现,把线程代码都放在&em&com&/em&内运行,再用vc写个dll创建...
 最近做一个类似动画编辑的工具,由于要有类似flash的播放功能,做的时候首先想到的是用windows的SetTimer定时器,也采用了这种方法,但后来测试发现播放不能实时.经过调试发现是SetTim...
这是COM多线程环境下我所遇到的第一个问题,谨此记录下来已被后忘。
问题大概这样:
一位同事在自己的组件中创建线程函数,在线程中调用我组件的方法,结果是间断性的发生CreateInst...
转载 http://www.cnblogs.com/jara/p/3468419.html
1.多线程的总结
不需要传递参数,也不需要返回参数
我们知道启动一...
没有更多推荐了,下次自动登录
现在的位置:
& 综合 & 正文
COM组件中的线程模式
提及COM的线程模式,实际上指的是两个方面,一个是客户程序的线程模式,一个是组件所支持的线程模式。客户程序的线程模式只有两种,单线程公寓(STA)和多线程公寓(MTA)。组件所支持的线程模式有四种:Single(单线程)、Apartment(STA)、Free(MTA)、Both(STA+MTA)。
公寓只是个逻辑上的概念。一个STA只能包含一个线程,一个MTA可以包含多个线程。一个进程可以包含多个STA,但只能有一个MTA。MTA中各线程可以并行的调用本公寓内实例化的组件,而不需要进行调度。跨公寓调用组件实例必须要进行调度。(除非使用了自由线程调度器)。
客户程序的线程是在调用CoInitializeEx()时决定客户线程的类型的。如果以参数 COINIT_APARTMENTTHREADED调用,则会创建一个STA公寓,客户线程包含在这个公寓里。如果以参数COINIT_MULTITHREADED调用,则创建一个MTA公寓,把线程加入到这个MTA中;如果进程内已经有了一个MTA,则不创建新的MTA,只把线程加入到已有的MTA。注意每个线程都必须调用CoInitializeEx()才能使用COM组件。
线程最重要的是同步问题。STA是通过窗口消息队列来解决这个问题的。当客户线程以 COINIT_APARTMENTTHREADED调用CoInitializeEx()时,将为会该STA创建一个具有 OleMainThreadWndClass窗口类的隐含窗口。所有对在这个公寓中建立的COM对象方法的调用都将都放到这个隐含窗口的消息队列中。所以每一个与STA相关联的线程必须用 GetMessage、DispatchMessage或类似方法来分派窗口消息。MTA内各线程可并行调用同一个组件对象的实例,从而不保证安全性,所以实现同步访问的责任就落在了组件身上。注意,STA的同步是公寓级的,就是说对公寓内不同组件的访问都要放到同一个消息队列中,对一个实例的方法调用会影响对其他实例的调用,所以并发程度很低。
在不同公寓间传递接口指针必须要经过调度。这主要还是为了同步对组件的调用。通过CoMarshalInterThreadInterfaceInStream和CoGetInterfaceAndReleaseStream实现。很简单。
Single型组件很特殊,它只能在一个单一的线程中执行。首先要说明的是一个进程中第一个以COINIT_APARTMENTTHREADED调用CoInitializeEx()的线程被称作是主STA。每次用CoCreateInstance()创建的Single型组件实际上都是创建在了这个主STA中,而不管是谁调用了CoCreateInstance()这个函数。所有对这个Single组件方法的调用都必须要通过这个主STA。
若STA创建STA型组件,是直接创建,直接调用。若STA创建MTA型组件,系统为组件创建一个MTA,STA通过代理访问组件。若STA创建Both型组件,是直接创建,直接调用。若MTA创建STA型组件,系统为组件创建一个STA,MTA通过代理访问组件。若MTA创建MTA型组件,是直接创建,直接调用。若MTA创建Both型组件,是直接创建,直接调用。可见如果客户程序和组件都支持同样的线程模式,那么COM就允许客户程序直接调用对象,这样将产生最佳性能。
Both型组件已经很好了,无论是STA还是MTA都可以直接创建调用它。但跨公寓的调用仍然要经过代理。为了更进一步以获得最佳性能,可以使用自由线程调度器(FTM)。注意其它类型的组件也可以使用FTM,只是由Both使用FTM可获得是最佳效果。FTM实现了接口IMarshal,当调度那两个调度接口指针的函数时,这两个函数(见5)内部调用IMarshal内的相关函数,并判断如果调度发生在一个进程内的公寓之间则直接返回接口指针;如果调度发生在进程之间或者远程计算机间,则调用标准的调度器,并返回指向代理对象的指针。所以可见使用FTM,即使是公寓之间也不用调度接口指针了!!
FTM虽然好,但使用FTM的组件必须遵守某些限制:(详情见书)使用FTM的对象不能直接拥有没有实现FTM的对象的接口指针;使用FTM的对象不能拥有其他公寓对象代理的引用。
全局接口表(GIT)。作用范围是进程内。可以把接口指针存进表中,然后在别的公寓内把其取出,GIT自动执行公寓间的调度,所以很方便。GIT是通过IGlobalInterfaceTable访问的。通过创建CLSID为CLSID_StdGlobalInterfaceTable的对象可调用它。
【上篇】【下篇】技术总监问我,在注册表关于组件多线程设置,在服务器找到已注册dll发现ThreadingModel键值,真不知什么意思,以下文章我看了真不懂,反正发现值是free,是支持多线程,以下备忘留用。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
关于COM组件线程模型的实验
线程模型是COM组件很重要而又不易理解的一个属性。本文尝试用简洁明了的描述和简单的实际例子来介绍COM组件的线程模型。
套间,英文为apartment,有的地方译作“套间”;有的译作“公寓”;还有的译作“单元”。本文采用“套间”这种译法。
套间是与COM组件密切相关的一个概念。Windows程序中,每个使用COM的线程都属于某个套间。套间是COM组件运行的逻辑线程上下文,决定了COM基础设施对位于其中的COM组件实施怎样的保护,提供怎样的同步服务。有三种套间类型:
l 单线程套间(STA,Single Thread Apartment)
l 多线程套间(MTA,Multiple Threads Apartment)
l 线程中立套间(TNA,Thread Neutral Apartment)
每个进程可以有多个STA,但是只能有一个MTA,也只能有一个TNA。线程调用CoInitializeEx()初始化COM基础设施时,第二个参数决定线程所属的套间类型:
l 如果第二个参数中带有COINIT_APARTMENTTHREADED标志,则COM基础设施为线程创建一个新的STA,线程将属于这个新创建的STA。如果这是进程中的第一个STA,则这个STA是进程的主STA。每个STA中有且只有一个线程。
l 如果第二个参数中带有COINIT_MULTITHREADED标志,则线程属于MTA。因为每个进程只能有一个MTA,所以所有MTA线程都在同一个MTA中运行。
l 线程中立套间中没有线程,也不是由线程创建的。COM基础设施为所有采用线程中立模型的组件创建线程中立套间。
也可以用CoInitialize()初始化COM基础设施,这个函数将为线程创建新的STA。
使用OLE功能的线程需要调用OleInitialize()初始化COM基础设施,这个函数将为线程创建新的STA。
一般而言,对于单线程套间,由于套间中只有一个线程在运行,所以不需要对其中的组件实施保护;而对于多线程套间,由于套间中有多个线程在运行,所以需要控制多个线程对组件的并发访问,也就是组件开发者必须编写代码处理多线程并发访问带来的同步问题。线程中立套间中虽然没有线程,但是任何线程可以自由地、直接地访问套间中的组件,所以也需要编写代码处理多线程并发访问带来的同步问题。
跨越套间的方法调用不能直接进行,而必须通过代理/桩基(proxy/stub)间接进行,这是COM编程的一个基本原则。所以,不能在不同套间直接传递接口指针,而必须通过列集/散集(marshal/unmarshal)间接传递。列集/散集过程就是让COM基础设施创建用于间接访问的代理/桩基的过程。
2 线程模型
线程模型是COM组件的一种属性,它决定COM组件可以存在于哪种或者哪些类型的套间中。线程模型不是在开发组件时通过程序代码指定的,而是通过注册表中的HKEY_CLASS_ROOT\CLSID\{clsid}\InprocServer32\ThreadingModel键值指定的,这是一个字符串类型的注册表键值,其中{clsid}是组件的GUID。当然,配置组件的线程模型时,要考虑组件的代码编写方式,而不应该随意指定。比如说,对于编码时没有考虑被多线程并发访问时数据保护的组件,就不应该配置为使用多线程模型。
COM组件可以被配置为使用五种线程模型之一:
2.1 单线程模型(Single)
在注册表中删除上述ThreadingModel键值,则COM组件被配置为使用单线程模型。使用单线程模型的组件只能存在于主STA,也就是进程中的第一个STA中。对于具有图形界面的Windows程序,第一个STA通常由主线程,也就是界面线程创建。具有图形界面的COM组件,比如说,ActiveX控件,常常使用单线程模型。
2.2 套间线程模型(Apartment)
设置上述ThreadingModel键值为Apartment,则COM组件被配置为使用套间线程模型。使用套间线程模型的组件只能存在于STA中。套间线程模型是在Visual
Studio中使用ATL开发COM组件时默认的线程模型。
单线程模型和套间线程模型的共同点是在任何时刻只有一个线程可以直接访问组件,这个线程就是创建组件所在的STA的线程(不一定是调用CoCreateInstance创建组件的线程)。其他线程对组件的调用都是通过这个线程间接进行的:COM基础设施为STA创建一个隐藏的窗口,将其他线程对STA中组件的调用请求转化为发送给这个窗口的消息,然后由套间中唯一的线程处理消息,返回调用结果。所以,使用单线程模型和套间线程模型的组件要求消息队列,其他线程对组件的调用都是间接地通过消息队列进行的。这一点很重要。本文后面将通过代码验证这一点。
2.3 自由线程模型(Free)
设置上述ThreadingModel键值为Free,则COM组件被配置为使用自由线程模型。使用自由线程模型的组件只能存在于MTA中,可以被处于MTA中的多个线程“自由”地调用。不在MTA中的线程调用MTA组件时,COM基础设施随机选择RPC线程池中的某个RPC线程代为间接处理(RPC线程池是COM基础设施的组成部分)。由于COM基础设施没有提供任何同步方面的帮助,多个线程可以并发地调用组件的方法,所以需要编写代码对组件实施必要的保护,就像多线程编程中需要对共享资源实施保护一样。
2.4 双线程模型(Both)
设置上述ThreadingModel键值为Both,则COM组件被配置为使用双线程模型。此时组件与创建组件的线程存在于相同的套间中:既可能是STA,也可能是MTA。因为组件可能存在于MTA中,被多个线程并发访问,所以需要编写代码对组件实施必要的保护。
自由线程模型和双线程模型有一个重要的差别:采用自由线程模型的组件可以创建能够直接调用组件的工作线程;而采用双线程模型的组件不能。因为采用双线程模型的组件可能位于STA中,如果组件创建的工作线程可以直接访问组件,则工作线程也必须位于STA中(套间之外的线程对组件的调用不能直接进行),这就违反了STA中只能有一个线程的规则,破坏了COM线程模型的同步机制。
2.5 线程中立模型(Neutral)
设置上述ThreadingModel键值为Neutral,则COM组件被配置为使用线程中立模型。使用线程中立模型的组件位于TNA中,可以被任何线程自由地、直接地访问。调用线程访问这种类型的组件时将暂时离开所属的STA或者MTA,进入TNA,直接对组件进行方法调用,调用完成后返回STA或者MTA。与采用自由线程模型和双线程模型的组件一样,必须编写代码对组件实施必要的保护,以防止多线程并发访问可能出现的问题。线程中立模型是运行在组件服务中的,不需要用户界面的组件的最优选择。
3 示例程序
笔者编写了一个简单的程序,使用生产者-消费者问题来演示COM组件线程模型与线程套间、消息循环的关系。程序中有一个生产者线程、多个消费者线程:生产者线程创建生产者COM组件,并用其创建产品——随机整数;每个消费者线程创建一个消费者COM组件,并通过该组件消费产品。生产者生产的产品和消费者消费的产品都会显示到程序界面上。而且,在启动生产者和消费者线程之前,可以通过程序界面指定各个线程采用的套间类型以及是否使用消息循环,还可以指定各个组件的线程模型。这样,通过观察程序输出,就可以了解到生产者和消费者是否正确地进行了同步,从而认识组件的线程模型与线程的套间类型、消息循环之间的关系。
3.1 生产者组件
生产者组件有两个方法:ProduceProduct()和GetNextProduct()。
生产者线程调用组件的ProduceProduct()方法生产一个产品,也就是生成一个随机整数,代码如下:
消费者线程中的消费者调用生产者的GetNextProduct()方法获取一个产品,代码如下:
注意这两个方法的代码都没有对共享数据进行同步访问控制。这样,在多个线程并发地调用这两个方法时,可能会发生同步方面的错误。
注意这里对Sleep()的调用:多线程程序设计中,即使程序没有正确进行同步,有时候似乎也不会发生问题。为了让没有正确进行同步时候的程序错误更快地暴露出来,这里增加了Sleep()调用。这样,一个消费者线程调用GetNextProduct(),执行到Sleep()语句时,进入休眠状态,休眠期间其他消费者线程可能再次调用GetNextProduct(),从而发生错误:再次消费同一个产品。
3.2 消费者组件
消费者组件只有一个方法ConsumeProduct(),代码如下:
方法调用生产者组件的GetNextProduct()方法获取下一个供消费的产品。多个消费者组件调用的是同一个生产者组件,而GetNextProduct()方法没有进行同步控制,所以多个消费者线程并发地调用时可能会取得错误的数据,也就是取得已经被其他消费者消费过的产品。
3.3 生产者线程
生产者线程创建生产者组件,每隔一定时间调用其ProduceProduct()方法生产一个产品,也就是产生一个随机整数,并且把这个整数输出到程序界面上的一个列表控件中。
生产者线程首先调用CoInitializeEx()初始化COM基础设施,其中第二个参数是从程序界面获取的,它指定了生产者线程采用的套间类型。
然后代码创建生产者并且调用CoMarshalInterThreadInterfaceInStream()将生产者接口指针列集到流接口指针pData-&pStream中,以便可以传递到随后创建的消费者线程中。COM编程中一个重要原则就是:不能直接在不同套间之间传递原始接口指针,而应该通过列集、散集来间接传递。使用CoMarshalInterThreadInterfaceInStream()和CoUnmarshalInterface()是列集、散集方法之一,此外还可以通过全局接口表(GIT,Global
Interface Table)进行列集、散集。
随后代码调用生产者每隔一定时间生产一个产品,并将其输出到界面上:
这段代码的关键在于MsgWaitForMultipleObjects()函数的参数及其返回值:
l 参数pData-&hReqExitEvent是一个事件句柄,界面通过设置它为授信状态来指示请求生产者线程退出;
l 参数pData-&dwProduceInterval是从程序界面获取的生产时间间隔。如果这个时间内没有其他条件满足使得MsgWaitForMultipleObjects()返回,则函数返回WAIT_TIMEOUT表示等待超时,随后代码会调用ProduceProduct()生产一个产品,并且输出到界面上;
l 参数pData-&dwProduceWakeMask用以指示是否使用消息队列:如果在界面上指定了使用消息队列,则其值为QS_ALLEVENTS,表示如果消息队列中有新消息等待处理,则MsgWaitForMultipleObjects()会返回WAIT_OBJECT_0
+ 1,随后代码会调用PeekMessage()获取消息,调用TranslateMessage()和DispatchMessage()处理消息。如果界面上没有指定使用消息队列,则参数pData-&dwProduceWakeMask的值为0,表示MsgWaitForMultipleObjects()不会因为有新消息等待处理而返回,也就是不使用消息循环。
3.4 消费者线程
消费者线程会执行下列处理:
l 创建下一个消费者线程;
l 创建消费者组件,通过其获取下一个待消费的产品,并且将其输出到界面上。
代码首先初始化COM基础设施,然后对流对象指针进行散集,取得生产者接口指针;随后再次对生产者接口指针进行列集,传递给下一个消费者线程;最后代码创建消费者对象,每隔一定时间获取消费一个产品,将其输出到界面上。
消费者线程主要代码如下:
3.5 实验及结果分析
程序界面如下:
可以通过各个控件进行各项设置,设置好之后点击【开始】则程序创建生产者线程和消费者线程,各个线程将在下方的列表中进行输出。列表的每一行代表一个产品,其中生产者列代表生产者生产了一个产品;各个消费者列代表一个消费者消费了一个产品。一段时间后点击【停止】,则退出各个线程。
通过观察列表控件的内容可以判断生产者和消费者是否正确地进行了同步,程序是否正确工作。如果如上图所示的那样,对于每一行,有且仅有一个消费者列的值与生产者列的值相等,则说明生产者生产的每个产品都仅仅被某个消费者消费了一次,程序是正确工作的。
3.5.1 单线程模型和套间线程模型
程序启动后不修改任何设置,直接点击【开始】,一段时间后点击【停止】,观察列表中的输出。可以发现:对于每一行,有且仅有一个消费者列的值与生产者列的值相等。这就说明了生产者和消费者之间正确地进行了同步。
如果不勾选生产者那一行后面的【使用消息循环】,然后点击【开始】,则程序输出20行后停止输出,而且消费者没有输出,这是为什么?
上文已经论述过,使用单线程模型的组件要求使用消息循环,因为组件所在套间之外的线程对组件的调用是通过消息间接进行的。如果选择不使用消息循环,则COM基础设施无法正确处理跨线程的调用,所以消费者线程无法正确工作;而生产者在生产的产品填满缓冲区之后也无法继续生产了,从而停止输出。
如果选择生产者线程使用多线程套间,不使用消息循环,点击【开始】后可以发现程序会正常工作:输出持续进行,并且每个产品只被某个消费者消费一次。程序正确工作的原因在于:生产者线程创建生产者对象的时候,COM基础设施发现请求创建组件的线程的套间类型与组件的线程模型不兼容。此时COM基础设施会创建一个新的STA,并且将新创建的生产者对象放到这个STA中,从而让生产者对象可以正确处理来自其他线程的调用请求。
如果选择生产者组件的线程模型是“套间线程模型(STA)”,程序的行为也是一样的。
单线程模型和套间线程模型非常相似:组件只能存在于STA中,只能有一个线程可以直接访问组件;从其他线程发起的对组件的调用,都是通过消息间接进行的,只有组件所在的STA中的线程正确处理了消息,调用才能正常进行。
单线程模型和套间线程模型的差别在于:采用套间线程模型的组件可以存在于任何STA中:可以是创建组件的线程所属的STA,也可以是COM基础设施帮助创建的STA;而采用单线程模型的组件只能存在于主STA中,也就是所有这种类型的组件都存在于进程中的第一个STA中,只能被创建第一个STA的线程直接访问。这种差别也可以用程序来验证:选择生产者组件使用单线程模型,生产者线程使用STA,不使用消息循环,但是选择界面线程使用单线程套间,观察发现程序可以正确工作。原因在于界面线程首先创建的进程中的第一个STA成为主STA,生产者组件将位于这个STA中,可以被其中的工作线程,也就是界面线程直接访问,而界面线程是有消息循环的,所以其他线程对生产者组件的访问可以正确地通过界面线程的消息循环间接进行。如果选择界面线程不使用COM,或者使用多线程套间,则会发现程序不能正确工作。
3.5.2 多线程模型
自由线程模型、双线程模型和线程中立模型都属于多线程模型,即可以有多个线程并发地、直接地访问组件,对组件进行方法调用。此时如果组件中没有处理同步的代码,则在访问共享数据的时候可能发生错误。以示例程序为例,选择生产者组件使用自由线程模型,则程序输出类似于下图:
有些行的各个消费者列没有内容,说明这一行对应的产品没有被消费;而有些行中有多个消费者列的值与生产者列的值相同,说明一个产品被消费了多次,也就是多个消费者线程之间没有进行正确的同步。这是因为示例程序中的生产者组件没有对并发访问进行同步。
如果选择生产者组件使用双线程模型,则可以发现:
1如果选择生产者线程使用单线程套间,在选择不使用消息循环时,程序输出20行之后就停止输出,而且各个消费者列没有内容;如果选择使用消息循环,则程序会产生正确的输出。
2 如果选择生产者线程使用多线程套间,则程序会产生错误的输出。
原因在于,使用双线程模型时,组件总是与创建组件的线程在相同套间中。这样,选择生产者线程使用单线程套间时,生产者组件在单线程套间中创建,只能被生产者线程直接访问;其他线程对生产者组件的调用需要通过生产者线程间接进行,而且要求生产者线程具有消息循环。如果没有消息循环,则生产者组件不能正确处理来自其他套间的调用,也就是消费者无法正确调用生产者组件来获取产品,所以消费者列没有输出;而生产者组件在产品缓冲区填满之后就停止生产了,所以也不再输出。如果选择生产者线程使用多线程套间,则生产者组件在多线程套间中创建,来自其他套间的调用由RPC线程池中的随机线程代为处理,可以被并发地调用。
上述分析的焦点在于生产者组件:由于生产者组件的代码没有对并发访问进行同步处理,所以在被并发访问时程序会产生错误的输出。但是,如果关注下消费者组件,则会发现用本文前面所论述的理论无法正确解释下图所示的情况:
这里,消费者线程使用单线程套间,消费者组件使用单线程模型。那么,所有消费者组件都在进程的主STA,也就是第一个消费者线程所在的STA中创建;只能被主STA中的线程,也就是第一个消费者线程直接访问。其他消费者线程由于在各自的STA中,无法直接访问自己创建的、位于主STA中的组件,而只能通过主STA线程间接访问。上图所示的情况没有选择消费者线程使用消息循环,那么,第一个消费者线程,也就是主STA线程没有消息循环,应该无法处理来自其他套间中的消费者线程对于消费者组件的调用。然而观察上图发现,各个消费者列都有输出,也就是其他消费者线程成功地对位于第一个消费者所在的STA(主STA)中的消费者组件进行了调用。这是问题一。抛开这个问题,就算其他消费者线程成功地对位于第一个消费者所在的STA(主STA)中的消费者组件进行了调用,这种调用也应该都是通过第一个消费者线程进行的,不存在并发访问的问题。然而上图所示的情况是:很多行的消费者列没有内容,而有的行里面各个消费者列都有内容,显然存在并发访问的情况。这是问题二。
笔者被这两个问题困扰了好几天,百思不得其解。后来,在完成博文之后,结合文章关于STA出调用的论述,经过思考,才为上图所示的情况找到了合理的解释。消费者组件的CConsumer::ConsumeProduct()方法会调用生产者组件的CProducer::GetNextProduct()方法,而消费者组件和生产者组件位于不同的套间,所以这是一个跨套间方法调用,相对于消费者组件所在的STA来说,是一个“出调用”。博文对于STA的出调用有以下论述:“调用离开STA时,COM会阻塞STA线程,但是让STA线程仍然可以处理回调。为了让回调可以发生,COM会跟踪每个方法调用的因果关系,以便能够识别何时应该释放正在RPC通道中等待某方法调用返回的STA线程,让其处理另一个进入的调用。默认情况下,STA入口有调用到达时,如果STA线程正在等待出调用返回,而且到达的入调用与正在等待返回的出调用不属于同一个因果链,则到达的入调用将阻塞。”COM怎样实现STA在等待出调用返回的同时,仍然可以处理回调的呢?由于STA线程是通过消息队列处理其他套间对于STA的入调用的,所以笔者猜测是通过消息等待函数MsgWaitForMultipleObjects或者MsgWaitForMultipleObjectsEx实现的。这样,STA线程在等待出调用返回的时候,仍然可以处理消息队列中新到达的消息。结合本文讨论的示例程序来看,第一个消费者线程在等待对于生产者组件的调用(出调用)返回的同时,可以处理消息队列中新到达的消息,也就是可以处理其他消费者线程对于主STA中消费者组件的调用。这样,多个消费者组件(各个消费者线程只调用自己创建的消费者组件)被并发地调用,然后多个消费者组件又并发地调用同一个生产者组件,所以就产生了上面的程序输出。当然,这里“并发”不太明显:所有对于多个消费者组件的调用最终都是由第一个消费者线程直接进行的,不存在并发。然而,生产者组件在MTA中,而消费者组件在STA中,因此消费者组件对于生产者组件的调用是通过RPC线程池里的随机RPC线程处理的,对生产者组件的多次调用很可能是由多个不同的RPC线程代为执行的。从这个角度看,就是“并发”调用了。
上述对于程序运行情况的解释,只是笔者的猜测,不一定正确。然而,使用上述思路可以解释生产者组件使用线程中立套间时程序的运行情况:
这一幅图展示的运行配置,与前一幅图只有一处不同:生产者组件的线程模型从“多线程模型”改成了“线程中立模型”。这一处不同使得只有第一个消费者线程有输出。原因在于,第一个消费者线程在调用使用线程中立模型的生产者组件时,临时离开线程所在的STA,进入到生产者组件所在的TNA中,直接对生产者组件进行调用。这样就不存在等待出调用返回的问题,也就不存在上述使用消息等待函数MsgWaitForMultipleObjects或者MsgWaitForMultipleObjectsEx的过程了。于是,其他消费者线程调用位于第一个消费者线程所在的STA(主STA)中的消费者组件时所投递的消息就一直在第一个消费者线程的消息队列中等待处理,使得其他消费者线程对于消费者组件的调用无法完成,不会返回,所以第一个消费者之外的其他消费者没有输出。
COM线程模型详解
http://blog.chinaunix.net/uid--id-3799415.html
线程模型是一种数学模型,专门针对多线程编程而...
COM的线程模型
COM,ThreadingModel
我们都知道,像“网络蚂蚁”之类的下载工具下载速度都很快,因为它们都是多线程下载的,也就是把一个下载文件分成若干份同时进行下载,而IE下载速度之所以这么慢,是因为它用的是单线程下载,那...
三十、安全设置1.关闭135端口Windows Registry Editor Version 5.00[HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Rpc]&DCOM...
必须注册项:
HKEY_CLASSES_ROOT\CLSID\
HKEY_CLASSES_ROOT\CLSID\{XXX}
所含项:名称为空的是提示名称,InfoTip项...
1.注册ocx控件: Regsvr32 [PATH]\xxx.ocx
2.利用Regedit.exe注册表编辑器,在编辑器的查找里直接输入 .OCX
文件名进行查找,找到:
“HKEY_CL...
ATL及COM线程的学习
呵呵,我刚刚开始我的博客。发现有时候用它记录自己学习的心得还有资源什么的,还是很不错的,否则到了要找的是时候,毫无头绪真的是很头疼。
第一篇博文吧,关于ATL及COM线程的学习,虽然看的还是懵懵懂懂,...
ArcGIS Engine 中的多线程使用
转自:http://anshien.blog.163.com/blog/static//
没有更多推荐了,

我要回帖

更多关于 AG8.COM 的文章

 

随机推荐