2014-09-26 2 views
2

Рассмотрят поточно-геттер в относительно, простейшей форме:путаницы с использованием обновляемого замка на станде :: Карта найдого/вставить

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; 
} 

в выше, я использую общий (обновляемый) замок на защищать операцию поиска и обновлять до уникальной блокировки, только если мне нужно вставить ключ/значение. Все идет нормально?

Что я представляю себе следующий (пожалуйста, дайте мне знать, если на любом этапе моя концепция не так):

  1. Два потока ввода метод

  2. Оба разрешено запускать repo.find() для один и тот же ключ одновременно (и ключ не существует).

  3. Оба отказались. Ключ не существует.

  4. первый поток получает монопольный доступ, введя модернизированную зону, в то время как второй поток, ждущий, чтобы войти модернизированную зону.

  5. первая нить заканчивает свою работу по созданию новой записи для ключа, а затем уходит.

  6. второй поток входит и перезаписывает ключ/значение, вставленное в первом потоке. (Это не то, что кто-то хочет)

Как решить эту проблему? спасибо

ответ

1

Одним словом, почти нет ничего плохого в вашем текущем решении.

Прежде всего, второй поток не будет перезаписывать данные, записанные первым, потому что map::insert() вставляет только новые ключи. Вам нужно только проверить, действительно ли 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; 
    } 
} 
+0

Спасибо за помощь. Кстати, как и во втором варианте, 'lower_bound' и' find' имеют логарифмическую временную сложность. то они должны принимать такое же количество времени. ? – rahman

+1

@rahman Я забыл изменить вызов 'insert', проверьте обновление. Он принимает итератор как подсказку и в этом случае имеет O (1) амортизированную сложность (см. Таблицу 102 в стандарте, ищите 'emplace_hint'). –

Смежные вопросы