2

这是实现IUnknown COM接口的Release方法的标准(不是推荐的)方式(直接取自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 被调度并获得对对象的新引用,例如通过调用QueryInterfaceAddRef
  • 线程1继续执行并运行delete this
  • 线程 2 留下了一个无效的对象

对我来说,确保一致性的唯一方法是创建一个标志,比如deleted,锁定整个临界区,即除了 return 之外的所有Release方法,并将标志设置为true

并在AddRefQueryInterface方法中检查此标志,如果已设置,则拒绝新引用的请求。

我错过了什么?

提前致谢。

4

2 回答 2

6

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

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

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

于 2013-10-02T17:47:16.730 回答
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导致未定义的行为(通常是“双重释放”错误)。

正确的实现是:

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)共享状态并且不安全。

另请参阅此问题的答案:为什么 COM IUnknown::Release 的这种实现有效?

于 2013-10-02T18:01:46.960 回答