windows - 当 2 个对象相互调用时,COM 如何避免死锁?
问题描述
假设有两个位于不同单元中的单元线程 COM 对象。或者他们可能完全处于不同的过程中。如果一个对象调用另一个对象的方法,而后者又调用第一个对象的方法,那么 COM 如何防止整个对象死锁?
解决方案
您所描述的称为重入。
事实上,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需要这样是完全有道理的,但我想我从来没有意识到这一点。
推荐阅读
- python - 在不知道我在寻找什么的情况下在字符串中查找模式的最佳方法?
- flutter - image_picker: ^0.7.2+1 使应用崩溃
- javascript - 使用赛普拉斯对 ul 类的每个孩子执行 .click() 吗?
- join - 内连接返回重复记录
- angularjs - 如何在 D3.js 的折线图中的图例中添加带有 Eclipse 的工具提示
- python - 多线程和多进程映射
- regex - Apache 规则重写映射正则表达式
- sql - SQL:如何根据边界条件选择某些值?
- angular - 无法在 Ionic 中进行生产 (--prod) 构建
- r - 如何并行化 xgboost 拟合?