原文:COM的多线程模型
COM的多线程模型是COM技术里头最难以理解的部分之一,很多书都有涉及但是都没有很好的讲清楚。很多新人都会在这里觉得很迷惑,google大神能搜到一篇vckbase上的文章,但是个人建议还是不要看的好几乎是胡说八道在乱搞。
COM自己其实并没有任何多线程模型,所以他用的多线程模型还是WIN32里头的那一套线程和同步对象。作为准备,这里先简单讲一下WIN32的线程和同步。作为惯例一讲WIN32的线程和同步对象就要把进程、线程这两个东西讲一遍,但是这里不讲,因为会看COM的对这部分已经很熟悉了,如果不熟悉的话建议也不要看COM了先回头看看《Windows核心编程》和《Windows高级编程》。WIN32的线程可以分为两种,UI线程和工作线程。UI线程是一种与一个窗口绑定的线程,其特点是包含一个窗口一个消息循环和一个窗口过程,由于消息循环的存在导致了其天生就具有一种同步机制:任何发送到该线程的消息都会被消息循环同步,不会有任何两个或以上的消息同时被窗口过程处理,所有消息都会被消息循环串行化;工作线程则可以认为是一个函数在一个线程上的一次运行,这种线程不具备任何自带的同步机制,如果要对两个工作者线程实施某种同步则只能使用WIN32的同步对象如CriticalSection或者Event等等。
接下来看COM的多线程模型,从VS2005的ATL工程向导上可以看到COM多线程模型分为这么几类:单线程(Single)、套间(Apartment)、两者(Both)、自由(Free)。这个部分个人觉得翻译不是很好,单线程(Single)个人认为翻译成单套间会比较好,原因后面有具体描述,但是作为尊重MS向导或者不至于更加把这部分弄得混乱,下面的术语还是引用MS向导上的讲法并且我尽可能使用英文术语。
可以看到COM多线程模型里最多用到的两个字是套间,那么先解释一下套间。套间可以根据他的英文想象一个房间,这个房间周围有墙,所以要进到这个房间必须用一种手段来穿透(通过门或者类似的东西),而这个房间里放的就是一个或者多个COM对象。对套间更理论性的解释是,在一个套间内存在一个或多个COM组件,而套间之间存在有一个明确的界限,并且套间内只存在唯一的一个套间线程,这个套间线程存在一个类似于消息循环(其实不应该用类似的,他就是一个隐藏了窗口的消息循环)来保证其天生所具有的同步性。看了这个定义你会觉得套间像什么?没错,一个只有一个主线程的Windows窗口应用程序进程。所以套间就是一个UI线程!做为UI线程他自然就能完成同步的功能。接下去分几个部分来讲这几种COM线程模型。
一、单线程(Single)
前面讲过这个模型最好是被翻译成单套间的好,因为这种多线程模型并不是说COM组件只能被用在单线程程序里头的,相反组件还是可以被正常的用在多线程程序里的。这种模型的真实意义是即使你的程序是多线程的并且在每个线程里都调用了CoInitalize(0,COINIT_APARTMENT),事实上在你的程序进程里头也只创建一个套间,并且把所有的组件都放到这个套间里头并由这个套间所拥有的消息循环来保证同步性。
或许这样讲不全面,但是上面一段确实讲了一种最简单的情况,就是所有的组件都按Single模型来创建。如果不是这样会什么情况呢,举个例子说A、B、C三个组件按Single模型创建,D按Apartment模型创建,并且四个组件分别在TA、TB、TC、TD四个线程里创建实例(每个线程都调用CoInitalize(0,COINIT_APARTMENT)来创建环境),那么组件A、B、C运行在由TA创建的套间里(TB、TC都没有创建套间,TA是这个套间的套间线程),而组件D则独立运行在TD创建的套间里(TD是这个套间的套间线程),这里一共就有了两个套间。这样应该是完整的情况了,再复杂的情况我想你都能推出来了。
二、套间(Apartment)
这种模型与前一种模型很相似,可以都被认为是创建WIN32概念上的UI线程,但是不同的在于,Single模型无论你在多少个线程里调用多少次CoInitalize(0,COINIT_APARTMENT)都只创建一个套间,套间的套间线程是你第一次调用CoInitalize(0,COINIT_APARTMENT)的线程,而Apartment模型则是你在一个线程上调用一个CoInitalize(0,COINIT_APARTMENT)就创建一个套间,并且把这个线程作为套间线程。
三、自由(Free)
这种模型就是工作者线程了,COM不再用消息循环来提供同步机制了。你要在多线程里使用,OK,那你自己给他做同步机制(或者由组件开发者把组件做成线程安全的)。你要单线程里使用,那更好无论如何都不需要同步了。
四、两者(Both)
这种模型保证了组件即能在套间模型使用也能在自由模型使用。例如说组件自身被创建为Apartment或者Single,但是使用者用CoInitalize(0,COINIT_MULITITHREAD)来创建环境,那么COM自己会再创建一个线程用CoInitalize(0,COINIT_APARTMENT)来创建环境供组件运行,反之亦然。
最后讲一下跨套间调用的问题,从上面的描述可以看到在Single和Apartment这两种模型里头套间内调用或者通过COM机制套间之间的通信都会被同步化,但是如何跨套间调用呢,比如说我们经常做这种事情,把一个存在在套间内组件的接口指针作为线程参数传递到另外一个线程中使用,或者两个组件存在于不同的套间中,但是由于连接点或者回调的原因需要互相调用。这个时候我们就需要使用proxy/stub机制,在传递接口指针之前调用CoMashalInterThreadInterface这个函数(我估计这个是所有WIN32API里函数名最长的函数了,MS真不让人过好日子-.-|||)来包装接口指针给PS dll来传递,然后再那个调用的线程或者套间里使用CoGetInterfaceAndReleaseStream来重新获得被调度过的接口指针,这样就能确保COM的线程同步机制能够正常的运行。
好了,全部讲完了,如果还不清楚建议去看一下《COM技术内幕》里的第十二章,这本书是我见过的所有书里对这部分描述得最好的一本(胜过《ATL技术内幕》),特别是里面那几张图,对理解这些模型非常有帮助,这书网上有电子版。