本文讲解COM提出的各个类型的线程模型,再说明COM运行时期库是如何实现它们的。
作者:lop5712 来源:论坛 2007年10月20日
关键字:
COM套间
Apartment被翻译成套间或是单元,是线程模型的一个实现者,就像在操作系统课程中讲到的线程只是一个数学模型,而Windows的线程、进程是它(数学模型的线程、进程)的实现者。套间只是逻辑上的一个概念,实现时只是一个结构(由COM管理)而已,记录着相关信息,如它的种类(只能是上面那三个,至少现在是),并由COM根据那个结构进行相应的处理。下面说明这三种套间的实现方式:
STA套间 一个套间如果是STA,那么那个套间有且只有一个线程和其关联,有多个对象或没有对象和其关联,就像有多个线程和一个进程关联一样,也就是说套间那个结构和某个线程及多个对象之间有关系,关系具体是什么由COM说得算,幸运的是COM正是按照上面的线程模型来定义互相之间关系的。根据上面的算法,很容易就知道只有这个线程可以访问这个套间里的对象。
COM是通过在STA套间里的线程中创建一个隐藏窗口,然后外界(这个套间外的线程)对这个对象的调用都转变成对那个隐藏窗口发送消息,然后由这个隐藏窗口的消息处理函数来实际调用组件对象的方法来实现STA的规则的。之所以使用一个隐藏窗口是为了方便组件代码的编写——只需调用DispatchMessage即可将方法调用的消息和普通的消息区分开来(通过隐藏窗口的消息处理函数)。外界对这个对象的调用都将转变成对这个隐藏窗口的消息发送来实现同步。至于COM如何截获外界对对象的调用,则是利于代理对象,后面再说明。
值得注意的是,如果使用标准汇集法生成代理对象,则代理对象会根据是进程内还是进程外的跨套间调用,来决定具体操作。如果外界线程和STA线程在同一进程内,则代理对象将直接向STA线程中的隐藏窗口发送消息;如果不在同一进程内(包括远程进程),代理对象将向RPC管理的一个线程池请求一个线程(RPC线程)来专门向另一进程中的STA线程的隐藏窗口发送消息,而不是代理对象直接发送消息,以防止外界线程由于网络等不稳定因素而导致挂起。
因为COM利用消息机制来实现STA,因此STA套间里的线程必须实现消息循环,否则COM将不能实现STA的要求。
MTA套间 这种类型的套间可以和多个线程及多个或没有对象相关联。根据上面的MTA模型,可知只有这个套间里的线程才能访问这个套间里的对象,和STA不同的只是可以多个线程同时访问对象。
外界(不属于这个套间的线程)对这个套间里的对象的调用将会导致调用线程(外界线程,也就是STA线程,因为NA没有线程)挂起,然后向RPC管理的一个线程池请求一个线程(RPC线程,并已经进入了这个MTA套间)以调用那个对象的方法。对象返回后,调用线程被唤醒,继续运行。虽然可以让STA线程直接调用对象(而不用像前述的挂起等待另一个线程来调用对象),但这是必须的,因为可能会有回调问题,比如这个MTA线程又反过来回调外界线程中的组件对象(假设客户本身也是一个组件对象,这正是连接点技术),如果异步回调将可能发生错误。
反过来,MTA的线程访问STA里的对象时,COM将把调用转换成对STA线程里那个隐藏窗口的一个消息发送,返回后再由COM转成结果返回给MTA的线程(如果使用标准汇集法生成标准代理对象,则发生的具体情况就如上面STA套间所述)。因此STA和MTA都是只能由它们关联的线程调用它们关联的对象。而根据上面所说,当MTA调STA或STA调MTA,都会发生线程切换,也就是说一个线程挂起而换成执行另一个线程。这是相当大的消耗(需要从内核模式向用户模式转换,再倒转好几回),而NA就是针对这个设计的。
NA套间 这种套间只和对象相关联,没有关联的线程,因此任何线程都可以直接访问里面的对象,不存在STA的还是MTA的。
外界(其实就是任何线程)对这个套间里面的调用都不需要挂起等待,而是进入NA套间,直接调用对象的方法。NA套间是由COM+提供的,COM+中的每个对象都有一个环境和其相绑定,环境记录了必要的信息,并监听对对象的每一次调用,以保证当将对象的接口指针成员变量进行传递或回调时其操作的正确性(保证执行线程在正确的套间内,MTA线程就是通过将自己挂起以等待STA线程的消息处理完毕来保证的),从而避免了调用线程的挂起,因此这个代理(其实也就是环境的一部分)被称作轻量级代理(相对于STA套间和MTA套间的重量级代理——需要挂起调用线程,发生线程切换)。
这个轻量级代理并不是永远都不发生线程切换。当NA对象里有个对指向一个STA对象的指针的调用而调用线程不是那个STA对象关联的线程时,调用将会转成向被调用的STA对象的关联线程发送消息,此时照样会发生线程切换。同理,如果那个对象是MTA的,而调用线程是STA线程时,依旧发生线程切换。不过除此以外的大多数情况(即不在NA对象的方法中调用另一个套间对象的方法)都不会发生线程切换,即使出现上面的情况也只有必要(MTA调NA再调MTA就不用切换)才切换线程。
根据上面所说,STA其实和MTA逻辑上是完全一样的,只是一个是关联一个线程,一个是关联多个线程而已。但把它们分开是必要的,因为线程安全就是针对是一个线程还是多个线程。而NA之所以不关联线程是因为它的目的是消除上面跨套间调用时产生的线程切换损耗,关联线程没有任何意义。
COM强行规定(不遵守也没辙,因为全是COM实现套间的,根本没有插手的余地)一个进程可以拥有多个STA的套间,但只能拥有一个MTA套间和一个NA套间,我想这应该已经很容易理解了(要两个MTA套间或NA套间干甚?)。