Рассмотрим поточно-безопасный метод получения в его относительно простой форме:
std::map<std::string, boost::shared_ptr<AAA> > repo;
AAA & get(const std::string &key)
{
boost::upgrade_lock<boost::shared_mutex> lock(repoMutex);
std::map<std::string, boost::shared_ptr<AAA> >::iterator it = repo.find(key);
if(it == repo.end()){
boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
boost::shared_ptr<AAA> t(new AAA(key));
repo.insert(std::make_pair(key,t));
return *t;
}
return *it->second;
}
выше я использую общую (обновляемую) блокировку для защиты операции поиска и обновляюсь до уникальной блокировки, только если мне нужно вставить ключ / значение. Все идет нормально?
Я представляю себе следующее (пожалуйста, дайте мне знать, если на каком-то этапе моя концепция неверна):
Два потока вводят метод
Оба могут работать repo.find()
для одного и того же ключа в то же время (и ключ не существует).
Оба они терпят неудачу. Так как ключ не существует.
первый поток получает эксклюзивный доступ, входя в обновленную область, в то время как второй поток, ожидающий входа в обновленную область.
Первый поток завершает свою работу по созданию новой записи для ключа, а затем
уходит
вторая нить входит и перезаписывает ключ / значение, вставленное первым потоком. (это не то, что кто-то хочет)
Как мы решаем эту проблему? Спасибо
Короче говоря, в вашем текущем решении нет ничего плохого.
Прежде всего, второй поток не будет перезаписывать данные, записанные первым, потому что карта :: вставить () вставляет только новые ключи. Вам нужно только проверить, если insert
действительно вставил элемент и вернул соответствующее значение.
Единственное беспокойство — возможное нежелательное создание t
ни за что. В этом случае вы можете добавить еще одну проверку после блокировки:
std::map<std::string, boost::shared_ptr<AAA> >::iterator it = repo.find(key);
if(it == repo.end()){
boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
if (repo.find(key) == repo.end() {
...
}
}
Но вы должны профилировать свой код, чтобы увидеть, дает ли это какое-либо преимущество.
Также вы можете использовать map::insert()
с подсказкой, чтобы избежать двойного поиска ключа:
std::map<std::string, boost::shared_ptr<AAA> >::iterator it = repo.find(key);
if(it == repo.end()){
boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
it = repo.lower_bound(key);
if (it->first != key)
boost::shared_ptr<AAA> t(new AAA(key));
repo.insert(it, std::make_pair(key,t));
return *t;
} else {
return *it->second;
}
}