当2个对象相互调用时,COM如何避免死锁?



假设有两个公寓线程COM对象,位于不同的公寓。或者它们完全处于不同的过程中。如果一个对象调用另一个对象的方法,而另一个对象又调用第一个对象的方法,COM如何防止整个对象死锁?

你所描述的叫做可重入性。

事实是COM没有做任何显式的事情来防止重入问题。这取决于每个对象的实现者在需要时采取预防措施。

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

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

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

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

最后,在COM中没有什么可以阻止你引起无限递归循环和随后的堆栈溢出。例如,取两个COM对象Obj1和Obj2,其中Obj2实现一个事件。我们可以让Obj1调用一个pObj2-> someemethod(…),这导致Obj2触发事件;然后让obj1监听("sink")该事件,并让该事件处理程序再次调用SomeMethod()。

更新:

非常感谢Remy Lebeau在他的评论中指出了一些我忘记讨论的东西,通过CodeGuru文章Understanding COM Apartments, Part I的链接,在这个过程中我也学到了一些我自己应该知道的新东西。

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

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

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

1这是完全有道理的,它需要这样,但我不认为我曾经意识到这一点。

相关内容

  • 没有找到相关文章

最新更新