套间实现规则
如前面所说,COM的套间机制要成功,必须服务器(组件)、客户和COM运行时期库三方面合力实现,其中有任何一方不按着规矩来,将不能实现套间机制的功能,不过这并不代表什么错误,套间机制不能运作并不代表程序会崩溃,只是不能和其他COM应用兼容而已。 比如:对象中的属性1在设计的算法中肯定不会被两个以上的线程写入,只是会被多个线程同时读出而已,因此不用同步,可以用MTA,但对象的属性2却可能被多个线程写入,因此决定使用STA。从而在客户端,通过前面说的CoMarshalInterface和CoUnmarshalInterface将对象指针传到那个只会写入对象的属性1的线程,其实这时就可以直接将对象指针传到这个线程,而不用想上面那样麻烦(而且增加了效率),但是就破坏了COM的套间规矩了——两个线程可以访问对象,但对象在STA套间中。所以?!!什么事都不会发生,因为已经准确知道这个算法不会捅娄子(线程访问冲突),即使破坏COM的规矩又怎样?!而且组件仍可以和其他客户兼容,因为不按规矩来的是客户,与组件无关。不过如果组件破坏规矩,那么它将不能和每一个客户兼容,但并不代表它和任何客户都不兼容。这里其实就是客户和组件联合起来欺骗了COM运行时期库。 上面的例子只是想帮助读者加深对套间的理解,实际中应该尽量保持和COM规范的兼容性(但不兼容并不代表是错误的)。客户要做的工作前面已经说过了(那两个函数或全局接口表或其他只要正确的方式),下面说明组件应该做的工作。组件可以存在于四个套间中(多了一个主STA套间),所需工作分别如下: STA 当一个组件是STA时,它必须同步保护全局变量和静态变量,即对全局变量和静态变量的访问应该用临界段或其他同步手段保护,因为操作全局和静态变量的代码可以被多个STA线程同时执行,所以那些代码的地方要进行保护。比如对象计数(注意,不是引用计数),代表当前组件生成的对象个数,当减为零时,组件被卸载。此变量一般被类厂对象使用,还好ATL和MFC已经帮我们实现了缺省类厂,这里一般不用担心,但自定义的全局或静态变量得自己处理。 主STA 与STA唯一的不同是这是傻瓜型的,连静态和全局变量都可以不用线程保护,因为所有不是安全访问静态和全局变量的对象都通过主线程(第一个调用CoInitialize的线程)的消息派送机制运行,因此不安全的访问都被集中到了一个线程的调用中,因而调用被序列化了,也就实现了对静态和全局变量的线程保护。至于为什么是主线程,因为进程要使用STA,则一定会创建主线程,所以一定可以创建主STA。因此主STA并不是什么第四种套间,只是一个STA套间,不过关联的是主线程而已,由于它可以被用作保护静态和全局变量而被单独提出来说明。因此一个进程内也只有一个主STA套间。 MTA 必须对组件中的每个成员和全局及静态变量的访问使用同步手段进行保护,还应考虑线程问题,即不是简单地保护访问即可,还应注意线程导致的错误的操作,最经典的就是IUnknown::Release()。
如果读者对自己多线程编程的技术没有信心,建议最好不要编写可以存在于MTA套间的组件,不过就不能获得MTA的高性能了。 在编写MTA时还应该注意到线程亲缘性(thread affinity)。没有线程亲缘性是指没有任何线程范围的成员变量,比如线程局部存储(TLS)、窗口句柄等。也就是说在MTA中不能保存任何记录着TLS 内存的指针或窗口句柄,如果保存将没有意义(比如A线程记录的内存空间对B线程来说是无效的,因为TLS构造了一个线程相关的内存空间,就像每个进程都有自己的私有空间)。而不幸地MFC在它的底层运作机制的实现中大量使用了TLS,如模块线程状态、线程状态等。正是由于这个原因,MFC不能编写在MTA中运行的组件。 NA 由于可能会多个线程同时访问NA套间的对象,因此和MTA一样,其不能有线程亲缘性并需要保护每个成员和全局及静态变量。而关于NA的轻量级代理,是由COM+运行时期库生成的,读者完全不用操心(只需将那个组件的ThreadingModel键值赋值为“Neutral”即可)。 前面提到过有一种进程内组件的ThreadingModel键值可以被赋为“Both”,这种组件很像NA,哪个套间都可能直接访问它,但只是可能,而NA组件是可以,这点可以从前面的那个进程内组件所属套间的规则表中看出。这种组件可以支持一种称作自由线程汇集器(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的代理能分别正确的指出需要让哪个线程执行回调操作,即向哪个线程发送消息,因此不能混用。 |
COM线程模型4
最新推荐文章于 2024-09-03 22:37:00 发布