首页 > 解决方案 > 当 2 个对象相互调用时,COM 如何避免死锁?

问题描述

假设有两个位于不同单元中的单元线程 COM 对象。或者他们可能完全处于不同的过程中。如果一个对象调用另一个对象的方法,而后者又调用第一个对象的方法,那么 COM 如何防止整个对象死锁?

标签: windowsmultithreadingwinapicom

解决方案


您所描述的称为重入。

事实上,COM 并没有做任何明确的事情来防止重入问题。由每个对象的实现者在需要时采取预防措施(如果适用)。

有趣的是,COM 中的重入在现实生活中远没有你想象的那么普遍。COM 中的对象图往往主要是树,它们不表现出可重入性。当您有循环时,几乎总是因为对象暴露了某种事件类型的功能,通常是连接点。

事件回调的范围非常有限,它们在每个对象代码的显式控制下触发,因此程序员可以轻松地对它们进行计时,以便它们发生在安全的地方(例如在方法主体的末尾/附近)工作完成)。这可以防止严重的重入问题的发展。

但是没有什么能阻止你编写危险的代码。例如,如果一个对象在其内部对象状态不一致的情况下触发了一个事件,那么所有的赌注都是关闭的。

你提到了死锁。死锁需要某种锁定机制(例如关键部分),并且由于上面列出的原因,在 COM 公寓中应该非常罕见甚至不可能。任何在持有锁的同时触发事件的对象都是在自找麻烦,死锁并不是它最大的担心:由于是 STA 对象,重入调用将在同一个线程上运行,并且它能够再次获取锁并继续执行,这意味着对象很可能会破坏其内部状态,导致崩溃或更糟。请注意,只有在对象的 STA 之外的线程可以访问由锁控制的资源时,STA 线程中的锁才有意义。

最后,COM 中没有任何东西可以阻止您导致无限递归循环和随后的堆栈溢出。例如,使用两个 COM 对象 Obj1 和 Obj2,其中 Obj2 实现一个事件。我们可以让 Obj1 调用 pObj2->SomeMethod(...) ,这会导致 Obj2 触发事件;然后让 obj1 监听(“接收器”)该事件,并让该事件处理程序再次调用 SomeMethod()。

更新:

非常感谢 Remy Lebeau 在他的评论中通过指向 CodeGuru 文章了解 COM 公寓,第一部分的链接指出了我忘记讨论的内容。在这个过程中,我自己也学到了一些我应该知道的新东西。

需要考虑重入和锁定的一个方面,那就是在单元间调用期间发生的事情(STA<->STA、STA<->MTA,甚至 STA<->OutofProc)。在公寓间呼叫期间,STA(呼叫者的)线程需要停止并等待对呼叫请求的答复;响应不能(根据定义)在同一个线程上执行。但它不能完全阻塞(例如WaitForSingleObject)等待响应,因为线程需要能够响应和处理不仅对原始对象的潜在回调,而且还对任何其他对象的回调同一公寓内的物体。如果它完全阻塞,COM 基础结构本身就会引入死锁的可能性,您甚至不需要对象之间的依赖循环。因此,COM 编组基础设施使用更复杂的等待形式,可以在其他一些情况下解除阻塞(Hans Passat 指出 CoWaitForMultipleHandles,这对我来说看起来很合适,但我不知道那个级别的基础设施)。如果发生适用的回调,编组基础设施将解除阻塞并允许该呼叫进入公寓并继续。

这是一种由 COM 基础结构本身引起的锁定形式,而不是作为对象实现的一部分显式编码的锁定形式,这就是我没有想到提出它的原因。所以 COM 实际上是“做一些事情来防止死锁”,但它是为了防止由它自己的基础设施引起的死锁可能性。

我没有意识到的部分是这种机制是非常有选择性的。它只允许构成同一因果链一部分的 COM 调用,即回调,这是线程正在等待的调用的直接结果。进入单元的其他 COM 调用必须排队等待该调用链结束,并等待 STA 线程返回到线程的消息循环。1

1需要这样是完全有道理的,但我想我从来没有意识到这一点。


推荐阅读