У меня есть класс с именем Root, который служит своего рода телефонной книгой для динамических вызовов методов: он содержит словарь URL-ключей, указывающих на объекты. Когда команда хочет выполнить данный метод, она вызывает экземпляр Root с URL-адресом и некоторым параметром:
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);
}
Из приведенного выше кода вы можете видеть, что этот метод "call" является не поточно-безопасным: объект "target" может быть удален между A и B, и мы не можем гарантировать, что "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)" не является методом, который должен изменять Root, либо путем изменения URL-адреса объекта, либо путем вставки новых объектов. Может помочь изменение области действия и использование мьютекса 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 it).