В чем разница между первой блокировкой и созданием Lock guard(adopt lock) и созданием уникального замка (defer lock) и блокировкой?
Я нашел следующие 2 части кода:
-
http://en.cppreference.com/w/cpp/thread/lock
void assign_lunch_partner(Employee &e1, Employee &e2) { // use std::lock to acquire two locks without worrying about // other calls to assign_lunch_partner deadlocking us { // m is the std::mutex field std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock); std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock); std::lock(lk1, lk2); // ... } }
-
http://www.amazon.com/C-Concurrency-Action-Practical-Multithreading/dp/1933988770
void swap(X& lhs, X&rhs){ if(&lhs == &rhs) return; // m is the std::mutex field std::lock(lhs.m, rhs.m); std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock); std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock); swap(lhs.some_detail, rhs.some_detail); }
Я хотел спросить, в чем разница и последствия использования любой из 2-х версий? (первая блокировка или первое создание std::lock_guard
или std::unique_lock
?)
3 ответов
1) первый пример кода
{
static std::mutex io_mutex;
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl;
}
это стандартный предохранитель замка, когда объем выйден, замок lk
освобожден
{
std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
std::lock(lk1, lk2);
std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
// ...
}
здесь мы сначала создаем замки, не приобретая их (это точка std::defer_lock
), а затем, используя std::lock
на обоих замках одновременно гарантирует, что они получены без риска взаимоблокировки, если другой вызывающий функцию чередует (мы могли бы иметь тупик, если вы заменили его двумя последовательными вызовами std::lock
:
{
std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
std::lock(lk1);
std::lock(lk2); // Risk of dedalock !
std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
// ...
}
2) второй пример кода
void swap(X& lhs, X&rhs){
if(&lhs == &rhs)
return;
// m is the std::mutex field
std::lock(lhs.m, rhs.m);
std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
swap(lhs.some_detail, rhs.some_detail);
}
теперь, здесь мы сначала приобретаем замки (все еще избегая тупиков), и затем мы создаем lockguards для того чтобы убеждаться что они правильно выпущены.
отметим, что std::adopt_lock
требует, чтобы текущий поток владел мьютексом (что имеет место, так как мы только что заблокировали их)
вывод
здесь есть 2 шаблона:
1) заблокируйте оба мьютекса одновременно, затем создайте охранников
2) Создайте охранников, затем заблокируйте оба мьютекса одновременно
оба шаблона эквивалентны и направлены на одно и то же : безопасно заблокировать два мьютекса одновременно и обеспечить, чтобы разблокировка всегда происходила для обоих.
что касается разницы между std::lock_guard
и std::unique_lock
, вы должны увидеть этот другой так пост, большую часть времени std::lock_guard
достаточно.
на самом деле есть абзац (3.2.6) в книги объясняя, что код практически эквивалентен, и вы можете заменить один на другой. Единственная разница в том, что std::unique_lock
имеет тенденцию занимать больше места и немного медленнее, чем std::lock_guard
.
суть в том, когда вам не нужна дополнительная гибкость, которая std::unique_lock
обеспечивает, идти с std::lock_guard
.
разница робастность против будущих изменений. В adopt_lock
версия существует окно, в котором мьютексы заблокированы, но не принадлежат обработчику очистки:
std::lock(lhs.m, rhs.m);
// <-- Bad news if someone adds junk here that can throw.
std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
также можно случайно удалить / опустить одно из объявлений guard без каких-либо ошибок во время компиляции. Проблема будет очевидна во время выполнения, когда тупик попадает, но не интересно отслеживать тупик обратно к его источнику.
на defer_lock
версия не страдает от все эти проблемы. Поскольку объекты guard объявлены до блокировка происходит, нет небезопасного окна. И, конечно, если вы опустите / удалите одно из объявлений guard, вы получите ошибку компилятора в std::lock
звонок.