Comments 3
В примере 1, по-моему, есть риск дедлока в Emit - в случае, если коллбэк деструктуриует одну из подписок (потому как используемый механизм блокировки - не reentrant; возможно, https://en.cppreference.com/w/cpp/thread/recursive_mutex поможет эту проблему преодолеть). Если вы используете этот код, попрубуйте прогнать тест, в котором коллбэк деструктуриует одну из подписок.
Да, все именно так. Если в процессе вызова Emit(), когда мьютекс уже захвачен, один из коллбэков вызовет код, который приводит к уничтожению подписки, то возникнет дедлок
int main()
{
Observable observable;
// Создаем подписку, чей коллбэк вызывает отписку (симулируем удаление подписки)
observable.Subscribe(
[ &observable ]()
{
// Так как emit() уже захватил мьютекс, попытка захватить его внутри отписки приведет к дедлоку
{
auto tmp = observable.Subscribe( []() {} );
}
} );
// Вызовем emit(), который вызовет наш коллбэк
observable.Emit();
// Любой код после не будет выполнен
}
Можно решить эту проблему через применение std::recursive_mutex, или копирование списка коллбэков под защитой мьютекса с последующим их вызовом вне критической секции.
Но если говорить об этом дальше, то стандартный std::mutex не учитывает и асинхронное переключение контекста, и может привести к ситуациям, когда корутина приостанавливается, удерживая блокировку, образуя дедлок. В таком случае нам уже надо смотреть в сторону чего-то по типу бустовых файбер-мьютексов, async_mutex-ов и т.д.
Если вы используете этот код...
Благо, не использую его вообще нигде, разве что для примеров в статьях. Спасибо, это очень дельное замечание, о котором мне, наверное, стоило упомянуть в статье.
Да, наверно, использование std:: синхронизаций в комбинации с coroutines - рискованное занятие - я, может быть, поразбираюсь после выхода C++26 с senders / receivers. По подписочным делам - возможно, какой-то хорошо отлаженный код есть в крупных корпоративных OSS библиотеках вроде abseil, folly, но наверняка я не уверен (с обоими имел только шапочное знакомство).
RAII 2.0: RAII как архитектурный инструмент в C++