我有一个名为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)。
答案 0 :(得分:2)
为了避免删除'target',我不得不写一个线程安全引用计数智能指针。这并不难。您需要确保的唯一事情是在关键部分中访问引用计数。有关详细信息,请参阅this post。
答案 1 :(得分:1)
你对此走错了路。请记住:您无法锁定数据,您只能阻止代码。您无法使用本地定义的互斥锁保护“对象”成员。在代码中需要完全相同的互斥锁来改变对象集合。当另一个线程正在执行call()方法时,它必须阻止该代码。必须至少在类范围内定义互斥锁。