困难的并发设计

时间:2010-01-21 10:18:56

标签: c++ mutex concurrency

我有一个名为Root的类,它用作动态方法调用的某种电话簿:它包含指向对象的url键的字典。当命令想要执行给定方法时,它会使用url和一些参数调用Root实例:

root_->call("/some/url", ...);

实际上,Root中的调用方法看起来很接近这个:

// Version 0
const Value call(const Url &url, const Value &val) {
  // A. find object
  if (!objects_.get(url.path(), &target))
    return ErrorValue(NOT_FOUND_ERROR, url.path());
  }

  // B. trigger the object's method
  return target->trigger(val);
}

从上面的代码中,您可以看到此“调用”方法线程安全:可以在A和B之间删除“target”对象,我们无法保证“objects_” “会员(字典)在我们阅读时不会改变。

我遇到的第一个解决方案是:

// Version I
const Value call(const Url &url, const Value &val) {
  // Lock Root object with a mutex
  ScopedLock lock(mutex_);

  // A. find object
  if (!objects_.get(url.path(), &target))
    return ErrorValue(NOT_FOUND_ERROR, url.path());
  }

  // B. trigger the object's method
  return target->trigger(val);
}

这很好,直到“target-> trigger(val)”是需要通过更改对象的url或插入新对象来更改Root的方法。修改范围并使用RW互斥体可以提供帮助(读取比Root上的写入多得多):

// Version II
const Value call(const Url &url, const Value &val) {
  // A. find object
  {
    // Use a RW lock with smaller scope
    ScopedRead lock(mutex_);
    if (!objects_.get(url.path(), &target))
      return ErrorValue(NOT_FOUND_ERROR, url.path());
    }
  }
  // ? What happens to 'target' here ?

  // B. trigger the object's method
  return target->trigger(val);
}

'目标'会发生什么?我们如何确保在查找和调用之间不会删除它?

一些想法:对象删除可以在Root中的消息队列中进行后处理。但是,我们需要在完整方法范围上删除另一个RW互斥锁读取锁定,并使用单独的线程来处理删除队列。

所有这些对我来说似乎都很复杂,我不确定并发设计是否必须看起来像这样,或者我只是没有正确的想法。

PS:代码是名为oscit的开源项目的一部分(OpenSoundControl)。

2 个答案:

答案 0 :(得分:2)

为了避免删除'target',我不得不写一个线程安全引用计数智能指针。这并不难。您需要确保的唯一事情是在关键部分中访问引用计数。有关详细信息,请参阅this post

答案 1 :(得分:1)

你对此走错了路。请记住:您无法锁定数据,您只能阻止代码。您无法使用本地定义的互斥锁保护“对象”成员。在代码中需要完全相同的互斥锁来改变对象集合。当另一个线程正在执行call()方法时,它必须阻止该代码。必须至少在类范围内定义互斥锁。