zoukankan      html  css  js  c++  java
  • com中的线程模式(转)

          提及COM的线程模式,实际上指的是两个方面,一个是客户程序的线程模式,一个是组件所支持的线程模式。客户程序的线程模式只有两种,单线程公寓(STA)和多线程公寓(MTA)。组件所支持的线程模式有四种:Single(单线程)、Apartment(STA)、Free(MTA)、Both(STA+MTA)。

      1、公寓只是个逻辑上的概念。一个STA只能包含一个线程,一个MTA可以包含多个线程。一个进程可以包含多个STA,但只能有一个MTA。MTA中各线程可以并行的调用本公寓内实例化的组件,而不需要进行调度。跨公寓调用组件实例必须要进行调度。(除非使用了自由线程调度器)

      2、客户程序的线程是在调用CoInitializeEx()时决定客户线程的类型的。如果以参数COINIT_APARTMENTTHREADED调用,则会创建一个STA公寓,客户线程包含在这个公寓里。如果以参数COINIT_MULTITHREADED调用,则创建一个MTA公寓,把线程加入到这个MTA中;如果进程内已经有了一个MTA,则不创建新的MTA,只把线程加入到已有的MTA。注意每个线程都必须调用CoInitializeEx()才能使用COM组件。

      3、线程最重要的是同步问题。STA是通过窗口消息队列来解决这个问题的。当客户线程以COINIT_APARTMENTTHREADED调用CoInitializeEx()时,将为会该STA创建一个具有OleMainThreadWndClass窗口类的隐含窗口。所有对在这个公寓中建立的COM对象方法的调用都将都放到这个隐含窗口的消息队列中。所以每一个与STA相关联的线程必须用GetMessage、DispatchMessage或类似方法来分派窗口消息。MTA内各线程可并行调用同一个组件对象的实例,从而不保证安全性,所以实现同步访问的责任就落在了组件身上。注意,STA的同步是公寓级的,就是说对公寓内不同组件的访问都要放到同一个消息队列中,对一个实例的方法调用会影响对其他实例的调用,所以并发程度很低。

      4、在不同公寓间传递接口指针必须要经过调度。这主要还是为了同步对组件的调用。通过CoMarshalInterThreadInterfaceInStream和CoGetInterfaceAndReleaseStream实现。很简单。

      5、Single型组件很特殊,它只能在一个单一的线程中执行。首先要说明的是一个进程中第一个以COINIT_APARTMENTTHREADED调用CoInitializeEx()的线程被称作是主STA。每次用CoCreateInstance()创建的Single型组件实际上都是创建在了这个主STA中,而不管是谁调用了CoCreateInstance()这个函数。所有对这个Single组件方法的调用都必须要通过这个主STA。

      6、若STA创建STA型组件,是直接创建,直接调用。若STA创建MTA型组件,系统为组件创建一个MTA,STA通过代理访问组件。若STA创建Both型组件,是直接创建,直接调用。若MTA创建STA型组件,系统为组件创建一个STA,MTA通过代理访问组件。若MTA创建MTA型组件,是直接创建,直接调用。若MTA创建Both型组件,是直接创建,直接调用。可见如果客户程序和组件都支持同样的线程模式,那么COM就允许客户程序直接调用对象,这样将产生最佳性能。

      7、Both型组件已经很好了,无论是STA还是MTA都可以直接创建调用它。但跨公寓的调用仍然要经过代理。为了更进一步以获得最佳性能,可以使用自由线程调度器(FTM)。注意其它类型的组件也可以使用FTM,只是由Both使用FTM可获得是最佳效果。FTM实现了接口IMarshal,当调度那两个调度接口指针的函数时,这两个函数(见5)内部调用IMarshal内的相关函数,并判断如果调度发生在一个进程内的公寓之间则直接返回接口指针;如果调度发生在进程之间或者远程计算机间,则调用标准的调度器,并返回指向代理对象的指针。所以可见使用FTM,即使是公寓之间也不用调度接口指针了!!

      8、FTM虽然好,但使用FTM的组件必须遵守某些限制:使用FTM的对象不能直接拥有没有实现FTM的对象的接口指针;使用FTM的对象不能拥有其他公寓对象代理的引用。

      9、全局接口表(GIT)。作用范围是进程内。可以把接口指针存进表中,然后在别的公寓内把其取出,GIT自动执行公寓间的调度,所以很方便。GIT是通过IGlobalInterfaceTable访问的。通过创建CLSID为CLSID_StdGlobalInterfaceTable的对象可调用它。
    1)COM对象为STA,Client线程初始化为COINIT_APARTMENTTHREADED
      COM Runtime将建立STA COM对象,Client的线程进入这个STA并直接得到COM对象指针.

    2)COM对象为STA,Client线程初始化为COINIT_MULTITHREADED
      如果这个COM对象没有被建立过,COM Runtime将为这个COM对象建立一个新的线程并在这个新线程中建立STA COM对象, Client线程进入的是MTA,得到的将是新的线程中这个COM对象的被marshal的指针.如果这个COM对象被建立过,COM Client线程得到的将是已经建立的STA线程中COM对象的被marshal的指针.

    3)COM对象为MTA,Client线程初始化为COINIT_APARTMENTTHREADED
      如果这个COM对象没有被建立过,COM Runtime将为这个COM对象建立一个新的线程并在这个新线程中建立MTA COM对象, COM Client线程进入STA,得到的将是新的线程中这个COM对象的被marshal的指针.如果这个COM对象被建立过,Client线程得到的将是已经建立的MTA线程中COM对象的被marshal的指针.

    4)COM对象为MTA,COM Client为COINIT_MULTITHREADED
      如果这个COM对象没有被建立过,COM Runtime将建立MTA COM对象.COM Client线程进入MTA并直接得到COM对象的指针.如果这个COM对象被初始化过, Client线程进入已经建立的MTA并直接得到COM对象的指针.

    5)COM对象为Any,COM Client为COINIT_APARTMENTTHREADED
      如果这个COM对象没有被初始化过,COM Runtime将建立STA COM对象,Client线程进入STA,直接得到COM对象指针.

    6)COM对象为Any,COM Client为COINIT_MULTITHREADED
      如果这个COM对象没有被初始化过,COM Runtime将建立MTA COM对象,Client线程进入这个MTA并直接得到COM对象的指针.如果这个COM对象被初始化过,Client线程进入已经建立的MTA并直接得到COM对象的指针.
    4. 关于marshal
      在COM中marshal分为三种: 进程内的marshal, 同一计算机中进程间的marshal, 以及不同计算机间的marshal. 进程内和进程间的marshal是通过Local RPC完成的,计算机间的marshal通过DCE RPC来完成. 进程间和计算机间的marshal是必须的,进程内marshal是在不同Apartment之间进行方法调用和传递对象Interface时发生.在同一Apartment内的调用用不着marshal. 举个例子来说, 一个处于MTA中的Client线程,想要调用一个处于STA中的对象时, COM Runtime会走进来, 对这个调用进行marshal.为什么要marshal? 因为MTA本身说明了现在是一个多线程的环境, 而STA中的对象不是Thread-safe的,那么对这个不是Thread-safe的对象的调用必须要序列化(排队).COM Runtime为了保证不是Thread-safe的对象的调用序列化, 必须要截获对该对象的调用, 然后进行排队. marshal就是起这个作用. 实际上,COM Runtime会截获MTA的线程对STA对象的调用(通过Proxy),将这个调用通过消息传递方式传递给STA对象的stub, 在完成调用后由stub将结果传回Proxy. 对于对象的Interface, 也是同样的道理.
           对于COM+的Thread-Neutral Apartment比较特殊, 它是一直需要marshal的, 一个方面是因为它处于不同的进程中.另外一个重要原因是, COM+提供了一系列新的功能, 如Object Pooling, Object Construct String等. COM+必须要截获Client对COM对象的调用才能完成将COM对象从缓冲池中取出以及放回缓冲池等的操作.
           那么marshal是自动还是手工完成的呢? 方法调用是自动完成的.对象的Interface的传递,一般情况下,  是自动完成的,比如你通过调用CoCreateInstanceEx,CoGetClassObject等得到的对象Interface,以及通过方法调用传递的对象Interface.但是有些情况下必须手工marshal. 还是形象写,举个例子: 比如我有一个COM Server, 它监视一个工控装置的信号, 如果信号有异常,它要通知客户端,让客户端进行报警动作.为了实现这个功能, 客户端和我的COM Server通过IConnectionPoint完成事件触发机制.为了提高性能, COM Server的主线程接受客户端通过IConnectionPoint的Advise传来的Interface指针, 并将之放到一个Interface指针表里, 主线程运行在STA中. 另外建立了一个运行于MTA中的线程专门用来监视信号,如果信号异常,它将调用Interface指针表里所有Interface的方法来通知客户端. 现在如果理解COM的机制的人看到我这个实现方法就知道这里面需要对Interface指针进行手工marshal. 为什么, 客户端通过IConnectionPoint传给我的COM Server主线程的Interface指针是自动进行了marshal, 但是, 由于我的COM Server的专门用来监视信号的线程运行在和主线程不同的Apartment之中, 对这个线程来说, 这些Interface指针是没有经过marshal的, 在调用是就会出现RPC_E_WRONG_THREAD错误. 要解决这个问题,有两个办法,
    1) 让我的主线程也运行在MTA中.这种方法简单.
    2) 手工marshal.
         在主线程中得到客户端的Interface指针后, 调用CoMarshalInterThreadInterfaceInStream, 得到一个IStream的指针,让后将它放到IStream的指针表里, 监视信号的线程要通知客户端时,从IStream的指针表取得IStream指针, 然后调用CoGetInterfaceAndReleaseStream得到marshal后的客户端Interface指针. 这种方法有个缺点, 就是一旦调用CoGetInterfaceAndReleaseStream后这个IStream指针就被释放掉了,下一次就取不到了. 更好的解决方法是采用GIT(global interface table), 主线程将它得到的Interface指针放到GIT, 监视信号的线程从GIT中取到的Interface指针是正确marshal了的. GIT是一个COM对象, 有三个方法提供Interface指针的存取,使用也很简单,这儿就不多说了,具体请参照帮助.
  • 相关阅读:
    mysql 函数 存储过程 事件(event) job 模板
    protobuf 无proto 解码 decode 语言 java python
    mitmproxy fiddler 抓包 填坑
    android adb 常用命令
    android机器人 模拟 踩坑过程
    RabbitMQ添加新用户并支持远程访问
    Windows下RabbitMQ安装及配置
    Java mybatis mysql 常用数据类型对应关系
    easyExcel 踩坑
    linux防火墙查看状态firewall、iptable
  • 原文地址:https://www.cnblogs.com/jyz/p/1294039.html
Copyright © 2011-2022 走看看