线程安全实现删除自身的对象

时间:2010-07-29 16:46:55

标签: c++ multithreading callback thread-safety mutex

我有一个从两个不同的线程调用的对象,在它被它们调用之后它会通过“删除它”来破坏它自己。

如何实现此线程安全?线程安全意味着对象永远不会完全破坏一次(它必须在第二次回调后自行销毁)。

我创建了一些示例代码:

class IThreadCallBack                                                                                             
{                                                                                                                 
  virtual void CallBack(int) = 0;                                                                                 
};                                                                                                                

class M: public IThreadCallBack                                                                                   
{                                                                                                                 
private:                                                                                                          
  bool t1_finished, t2_finished;                                                                                  

public:                                                                                                           

  M(): t1_finished(false), t2_finished(false)                                                                     
  {                                                                                                               
    startMyThread(this, 1);                                                                                       
    startMyThread(this, 2);                                                                                       
  }                                                                                                               

  void CallBack(int id)                                                                                           
  {                                                                                                               
    if (id == 1)                                                                                                  
    {                                                                                                             
      t1_finished = true;                                                                                         
    }                                                                                                             
    else                                                                                                          
    {                                                                                                             
      t2_finished = true;                                                                                         
    }                                                                                                             

    if (t1_finished && t2_finished)                                                                               
    {                                                                                                             
      delete this;                                                                                                
    }                                                                                                             
  }                                                                                                               
};                                                                                                                

int main(int argc, char **argv) {                                                                                 
  M* MObj = new M();                                                                                              
  while(true);                                                                                                    
}                                                                                                                 

显然我不能使用Mutex作为对象的成员并锁定删除,因为这也会删除Mutex。另一方面,如果我在设置了finised-flag的互斥保护区域内设置了“toBeDeleted”-flag,我不确定是否存在根本没有删除对象的情况。 请注意,线程实现确保在任何情况下每个线程都会调用一次回调方法。

编辑/更新: 如果我将Callback(..)更改为:

,该怎么办?
void CallBack(int id)

{    
  mMutex.Obtain()
  if (id == 1)
  {
    t1_finished = true;
  }
  else
  {
    t2_finished = true;
  }
  bool both_finished = (t1_finished && t2_finished);

  mMutex.Release();

  if (both_finished)
  {
    delete this;     
  }
}

这可以被认为是安全的吗? (使用mMutex是m类的成员?)

我认为,如果我在发布互斥锁后没有访问任何成员?!

5 个答案:

答案 0 :(得分:3)

您可以考虑将计数器设置为您正在等待的线程数,然后使用互锁减量,而不是使用两个单独的标志。

然后你可以100%确定当线程计数器达到0时,你已经完成并且应该清理。

有关互锁减量on Windowson Linuxon Mac的更多信息。

答案 1 :(得分:3)

使用Boost's Smart Pointer。它会自动处理;你的对象不必自行删除,而且它是线程安全的。

修改
从您上面发布的代码中,我不能说,需要更多信息。但你可以这样做:每个线程都有一个shared_ptr对象,当调用回调时,你调用shared_ptr :: reset()。最后一次重置将删除M.每个shared_ptr可以与thread local storeage一起存储在每个线程中。所以从本质上讲,每个线程都负责自己的shared_ptr。

答案 2 :(得分:2)

我曾经通过以下方式操作,实现了类似这样的东西,完全避免了delete this的愚蠢和混乱:

  • 启动一个负责删除这些等待条件的共享对象的线程
  • 当不再使用共享对象时,不要将其自身删除,让它将自身插入到线程安全队列中并发出删除线程正在等待的条件
  • 当删除线程唤醒时,它会删除队列中的所有内容

如果您的程序有一个事件循环,您可以通过创建一个意味着“删除未使用的共享对象”的事件类型来避免为此创建一个单独的线程,并让一些持久对象以与该事件相同的方式响应此事件。删除线程将在上面的例子中。

答案 3 :(得分:2)

我无法想象这是可能的,尤其是在班级本身。问题有两个:

1)没有办法通知外界不要调用该对象,因此如果指针被删除,外部世界必须负责在调用“CallBack”后将指针设置为0。

2)一旦两个线程进入此功能,你就是,原谅我的法语,绝对性交。在已删除的对象上调用函数是UB,只需想象当某人在其中时删除对象会导致什么。

我从未见过“删除这个”只不过是令人憎恶的东西。并不意味着有时候,在非常罕见的条件下,这是必要的。问题是人们做得太多,不考虑这种设计的后果。

我不认为“被删除”会运作良好。它可能适用于两个线程,但三个呢?您无法保护调用删除的代码部分,因为您正在删除保护(如您所述),并且因为您将不可避免地导致UB。所以第一次通过,设置标志并中止....其余的将在出路时调用删除?

答案 4 :(得分:1)

更强大的实现将是实现引用计数。对于你开始的每个线程,增加一个计数器;对于每个回调调用,减少计数器,如果计数器已达到零,则删除该对象。您可以lock计数器访问权限,或者您可以使用Interlocked类来保护计数器访问权限,但在这种情况下,您需要注意第一次线程完成和第二次启动之间的潜在竞争。

更新:当然,我完全忽略了这是C ++的事实。 :-)您应该使用InterlockExchange更新计数器而不是C#Interlocked类。