Agility与thread-safety区别详解:为何Agility不隐含线程安全?
敏捷性(Agility)与线程安全的核心区别
两者是完全独立的属性,解决的是COM/WinRT运行时中不同层面的问题:
- 敏捷性:属于对象的跨Apartment访问属性。一个敏捷对象的接口指针可以直接在不同的COM Apartment(包括单线程STA、多线程MTA)之间传递使用,不需要执行列集(Marshal)/拆集(Unmarshal)操作,也不会触发
RPC_E_WRONG_THREAD这类跨线程访问错误。它解决的是「指针在其他线程上能不能用」的问题。 - 线程安全:属于对象内部状态的并发访问属性。一个线程安全的对象在多线程同时调用其成员方法、读写内部状态时,不会出现数据竞争、状态异常、未定义行为,无需用户额外加同步锁即可安全并发访问。它解决的是「指针能用的时候并发调用会不会出错」的问题。
为什么敏捷性不隐含线程安全
因为敏捷性的实现仅作用于COM底层的跨Apartment通信逻辑,和对象本身的业务实现完全解耦:
标记对象为敏捷,只是告诉COM运行时不需要为这个对象的跨Apartment调用做RPC转发,允许不同线程直接访问对象的内存实例。运行时不会对对象的内部方法调用加任何锁,也不会帮你处理并发访问的竞争问题。
简单来说,敏捷性只是帮你省去了跨线程传递COM指针的繁琐操作,对象本身的并发安全性完全由其具体实现决定,两者没有任何必然关联。
C++ COM场景下的具体表现
以ATL开发原生COM对象为例,你可以通过DECLARE_AGILE()宏将对象标记为敏捷,示例代码如下:
class ATL_NO_VTABLE CAgileCounter : public CComObjectRootEx<CComMultiThreadModel>, public CComCoClass<CAgileCounter, &CLSID_AgileCounter>, public IAgileCounter { public: DECLARE_AGILE() // 标记对象为敏捷,支持跨Apartment直接传指针 DECLARE_NOT_AGGREGATABLE(CAgileCounter) BEGIN_COM_MAP(CAgileCounter) COM_INTERFACE_ENTRY(IAgileCounter) END_COM_MAP() // 计数器累加方法,未做任何同步保护 HRESULT STDMETHODCALLTYPE Increment() override { m_count += 1; return S_OK; } HRESULT STDMETHODCALLTYPE GetCount(LONG* out) override { *out = m_count; return S_OK; } private: LONG m_count = 0; };
这个对象的实际表现为:
- 你在STA线程创建对象后,直接把接口指针传递给MTA线程调用
Increment,不会出现跨线程错误,符合敏捷对象的特性; - 如果你同时启动100个线程各调用100次
Increment,最终GetCount返回的结果大概率远小于10000,因为m_count +=1存在数据竞争,对象完全不具备线程安全性。
另外常见的认知误区是:使用winrt::agile_ref包装COM指针、或者使用支持多线程的COM线程模型,就等同于线程安全,实际上这些能力都只保证COM底层的引用计数、指针跨Apartment传递的合法性,和对象业务逻辑的并发安全没有任何关系。
内容的提问来源于stack exchange,提问作者user15284017
相关产品推荐
相关产品推荐

