IUnknown.Release标准实现竞争条件?

时间:2013-10-02 17:03:05

标签: c++ windows multithreading winapi com

以下是实施 IUnknown COM 发布 方法的标准(不推荐)方式接口(直接取自MSDN):

ULONG CMyMAPIObject::Release()
{
    // Decrement the object's internal counter.
    ULONG ulRefCount = InterlockedDecrement(m_cRef);
    if (0 == m_cRef)
    {
        delete this;
    }
    return ulRefCount;
}

我想知道如果公寓模型不是 STA ,是否会出现竞争条件:

  • 说有一个参考文献
  • 线程1通过调用 Release
  • 释放它
  • 它运行并在delete this
  • 之前停止
  • 调度线程2并获得对该对象的新引用,例如,通过调用 QueryInterface AddRef
  • 线程1继续执行并运行delete this
  • 线程2留下无效对象

对我来说,确保一致性的唯一方法是创建一个标志,比如说已删除,锁定整个关键部分,即除了返回之外的所有 Release 方法,并将标志设置为 true

AddRef QueryInterface 方法中检查此标志,如果已设置,则拒绝新引用请求。

我错过了什么?

提前致谢。

2 个答案:

答案 0 :(得分:6)

  

调度线程2并获得对该对象的新引用,例如通过调用QueryInterface或AddRef

只有在已经有对IUnknown或对象实现的其他接口之一的引用时才能执行此操作。之前进行了AddRef()调用。因此,引用计数可以永远通过另一个线程的Release调用减少到小于1的值。

正确编写代码,必须将ulRefCount与0进行比较,而不是m_cRef。

答案 1 :(得分:4)

代码中存在竞争条件,但它不是您示例中的竞争条件。 Release()的此实现具有可能导致未定义行为的竞争条件(通常为“双重释放”)。考虑:

  1. 线程1&线程2具有对象的引用(m_cRef == 2)
  2. 线程1调用Release()并在运行InterlockedDecrement()后立即中断(m_cRef == 2)
  3. 线程2调用Release()并运行完成,因此m_cRef == 0因此调用delete this
  4. 线程1在行if (0 == m_cRef)m_cRef == 0处继续,因此它再次调用delete this导致未定义的行为(通常是“双重释放”错误)。
  5. 正确的实施是:

    ULONG CMyMAPIObject::Release()
    {
        // Decrement the object's internal counter.
        ULONG ulRefCount = InterlockedDecrement(m_cRef);
        if (0 == ulRefCount) //<<<< THIS FIXES THE PROBLEM
        {
            delete this;
        }
        return ulRefCount;
    }
    

    if检查需要在本地ulRefCount变量上。因为InterlockedDecrement()返回它递减的值,所以ulRefCount在线程2的调用上只会为零,所以delete this只会在线程2上调用。

    if (0 == m_cRef)正在访问没有锁定的共享状态,并且不安全。

    同时查看此问题的答案:Why does this implementation of COM IUnknown::Release work?