【问题标题】:reference counted class and multithreading引用计数类和多线程
【发布时间】:2014-01-14 16:47:29
【问题描述】:

我是多线程编程的新手,我仍然对此感到困惑。

下面是我的引用计数类:

class Rbuffer
{
  private:
    char *m_pnData;
    volatile unsigned int mRefCount;

  public:
     Rbuffer(int nLength) : mRefCount(0)
    {
    m_pnData = new char[nLength]; 
    }
   ~Rbuffer(){
    delete[] m_pnData;
    }

   void decRef() {
     if(InterlockedDecrement(&mRefCount)==0){
               delete (Rbuffer *)this;
           }
    }

  void incRef() {
        InterlockedIncrement(&mRefCount);
    } 

}; 

它是完全线程安全的吗?你能排除这种情况吗:

ThreadA                                 ThreadB
PointerToRBuffer->incRef();//mRefCount 1
switch->  
                                        PointerToRBuffer->incRef();//mRefCount 2
                                          <-switch
PointerToRBuffer->decRef();           
InterlockedDecrement(&mRefCount)//mRefCount 1 
switch->                                
                                        PointerToRBuffer->decRef();//mRefCount 0!
                                        InterlockedDecrement(&mRefCount);
                                        if (0==0)
                                        delete (Rbuffer *)this; 
                                            <-switch
if (0==0) 
//deleting object, that doesn't exist 
delete (Rbuffer *)this;
//CRASH                               

崩溃的原因可能是只有 (InterlockedDecrement(&mRefCount)) 部分是原子的,但 if (InterlockedDecrement(&mRefCount)==0) 不是? 我上面的例子错了吗?

提前感谢您的意见和建议,以使我的课程完全线程安全。

【问题讨论】:

  • 您正在删除一个非动态成员变量 (delete[] m_pnData),并且没有为保护实例可以自毁的类的构造提供任何启示(即具有私有的静态类工厂方法)构造函数家族)。也就是说,我认为您的崩溃可能与引用计数无关。坦率地说,delete 操作数上的固定转换应该同样令人担忧。我认为您没有为此使用 std::shared_ptr&lt;Rbuffer&gt; 是有原因的,因为这会使 all 这一切变得无关紧要。

标签: c++ multithreading reference-counting


【解决方案1】:

您的分析不正确;您发布的代码正确使用了interlockedDecrement

这是一个安全的使用

 if(InterlockedDecrement(&mRefCount)==0)
           cleanup();

..但这确实会出现您描述的问题

 InterlockedDecrement(&mRefCount);

 if (mRefCount==0)
           cleanup();

但是,delete this 的使用更可能是问题的原因。您不太可能通过此处描述的“绝对肯定 100% 肯定”测试: http://www.parashift.com/c++-faq-lite/delete-this.html

尤其是下面的简单代码会造成混乱。

{
  RBuffer x;  // count is what... ? zero
  x.incRef(); // make count one
  x.decRef(); // make count zero, and deletes itself 
}  // now x goes out of scope, so destructor is called a second time = chaos! 

一个普通的“引用计数”习语涉及一个“共享对象”(带有计数)和简单的“引用对象”(不是 C++ 引用,尽管语义相似),它们引用共享对象。 “引用对象”的构造函数和析构函数负责调用共享对象上的incref/decref 方法。所以共享对象会自动计算活动“引用对象”的数量。

【讨论】:

  • mRefCount 的成员访问权限在第一个示例中同样存在问题。如果cleanup() 删除了实例,它也删除了可寻址的mRefCount(因为它不再存在),因此在线程A 执行delete this; 掩埋之后,线程B 的上下文切换进入上述if 表达式eval 切换cleanup() 内部仍然会出错。如果这是您在描述中提到的问题,我同意。
  • @WhozCraig 是正确的,这就是为什么我建议围绕整个 decRef 操作使用互斥锁,并将 PointerToRBuffer 设置为 NULL 并在取消引用 PointerToRBuffer 之前添加 NULL 检查。
  • @ChrisDesjardins 完全正确。如果 A 在 B 可以调用 interlocked-dec.. 之前删除了对象,它将 取消引用无效地址。
  • @Roddy “取消引用在哪里?”你认为InterlockedDecrement(&amp;mRefCount) 在做什么?它将&amp;this-&gt;mRefCount 发送到InterlockedDecrement。现在考虑是否有可能this 在完成后不再有效(并且它可能的)。
  • @WhozCraig 我认为你错过了这是用于引用计数的一点:如果在对象被删除后调用 any 方法,那么它就是不好,即使是单线程的。
【解决方案2】:

目前还不是 100% 清楚发生了什么,但看起来 ThreadA 删除了 RBuffer 对象,然后 ThreadB 取消了它的引用。

您真正需要的是关于减量和删除操作的互斥,此外您需要设置某种标志以防止删除后取消引用。通常将指针设置为 NULL 是可行的,然后在取消引用之前检查 NULL。

所以你的 decRef 可能看起来像这样:

 void decRef() {
     lock(_mutex);
     if(InterlockedDecrement(&mRefCount)==0) {
               PointerToRBuffer = NULL;
               delete (Rbuffer *)this;
           }
    }

使用 shared_ptr 可能会更好。

【讨论】:

  • 为什么(以及如何)decref(类方法)需要修改PointerToRBuffer(指向该类实例的指针)?
  • @Roddy 我知道这整件事很糟糕,这就是为什么应该考虑使用 shared_ptr 的原因。
  • 毫无疑问 Shared_ptr 是正确的解决方案!但是学习如何实现shared_ptr 是一项非常好的学习技能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-18
  • 2010-09-15
  • 1970-01-01
  • 1970-01-01
  • 2016-09-06
相关资源
最近更新 更多