Не держите блокировки или другие примитивы синхронизации через точки приостановки
Причина
Этот паттерн создаёт значительный риск взаимных блокировок. Некоторые типы ожиданий позволят текущему потоку выполнять дополнительную работу до завершения асинхронной операции. Если поток, удерживающий блокировку, выполняет работу, требующую той же блокировки, то произойдёт взаимная блокировка, потому что он пытается получить блокировку, которую уже удерживает.
Если корутина завершится в другом потоке от потока, который получил блокировку, то это неопределённое поведение. Даже с явным возвратом в исходный поток исключение может быть выброшено до возобновления корутины, и в результате охранник блокировки не будет удалён.
Плохой пример
std::mutex g_lock;
std::future<void> Class::do_something()
{
std::lock_guard<std::mutex> guard(g_lock);
co_await something(); // DANGER: coroutine has suspended execution while holding a lock
co_await somethingElse();
}
Хороший пример
std::mutex g_lock;
std::future<void> Class::do_something()
{
{
std::lock_guard<std::mutex> guard(g_lock);
// modify data protected by lock
}
co_await something(); // OK: lock has been released before coroutine suspends
co_await somethingElse();
}
Примечание
Этот паттерн также плох для производительности. Когда достигается точка приостановки, такая как co_await, выполнение текущей функции останавливается и начинает выполняться другой код. Может пройти много времени до возобновления корутины. На протяжении всей этой длительности блокировка будет удержана и не сможет быть получена другими потоками для выполнения работы.
Применение
Отмечайте все охранники блокировок, которые не удаляются до приостановки корутины.