【问题标题】:How does COM avoid deadlocks when 2 objects call into each other?当 2 个对象相互调用时,COM 如何避免死锁?
【发布时间】:2021-12-06 15:27:28
【问题描述】:

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

【问题讨论】:

  • 如果公寓线程 - 通过向另一个公寓的 com 窗口发送或发布消息来实现调用,然后等待消息响应。但直到等待线程可以接收传入的窗口消息 - 所以处理传入的调用
  • 是在幕后调用PeekMessage还是什么?
  • @user15284017 见Understanding COM Apartments, Part I(在图 2 之后向下滚动大约 1/4)
  • 如果您在 STA(UI 线程)上对另一个单元/线程或进程上的对象进行传出编组 COM 调用,COM 将简单地运行一个迷你 Windows 消息泵(GetMessage + DispatchMessage)来允许传入的 COM 调用返回到同一个线程。或者正如您在 cmets 中提到的那样,“在幕后调用 PeekMessage 或其他什么。”.
  • @sol如何设计一个系统,让两方之间的相互通信不需要两方之间的相互通信?

标签: windows multithreading winapi com


【解决方案1】:

您所描述的称为重入。

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

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

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

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

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

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

更新:

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-16
    • 1970-01-01
    • 2013-06-07
    • 2012-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多