Реализация SRWLock (Slim Reader/Writer Lock) на Windows может привести к серьёзным проблемам: многопоточное приложение может зависнуть (deadlock).
При частой конкуренции многих потоков, удерживающих shared_lock, и хотя бы одного потока, пытающегося получить unique_lock, возможно возникновение ситуации, когда потоки оказываются в состоянии взаимного ожидания. Код выглядит абсолютно корректным, но приложение зависает внутри вызова в WinAPI.
Это касается любых многопоточных программ для Windows, которые явно либо неявно используют SRWLock для синхронизации. В том числе написанных на старых реализациях Rust, пока в Rust не заменили реализацию для Windows, отказавшись от SRWLock (GitHub Issue). В том числе написанных на C# (фикса пока нет) и т.д.
Краткое описание проблемы
Внутреннее состояние блокировки и очередей ожидания может привести к состоянию, при котором:
* Несколько потоков удерживают shared-блокировку.
* Поток, запрашивающий эксклюзивный доступ (unique), зависает в ожидании.
* Состояния очередей и внутренних флагов SRWLock могут привести к тому, что ни читатели, ни писатель не проснулись, создавая deadlock.
Часто такие проблемы всплывают под большими нагрузками, в условиях множества работающих потоков.
Можно ли как-то обойти проблему?
Использовать другие примитивы:
Отказаться от std::shared_mutex. Можно применить классический std::mutex или же использовать собственную реализацию reader-writer локов, основанную на атомарных операциях, condition_variable и прочих механизмах.Следить за обновлениями:
Возможно, в будущих обновлениях Windows или Microsoft STL появятся исправления. Подписывайтесь на соответствующие репозитории (например, microsoft/STL), чтобы быть в курсе актуальных изменений и патчей. Но раньше Windows 12 фикс пока не планируется.Рассмотреть альтернативные подходы к синхронизации данных:
Современные высоконагруженные системы нередко выбирают lock-free структуры данных, RCU (Read-Copy-Update) или другие схемы, снижающие вероятность зависания из-за проблем в примитивах синхронизации.
Пруфы
StackOverflow:
std::shared_mutex::unlock_shared() — unlock_shared() блокируется, хотя активных эксклюзивных блокировок нет.
Reddit /r/cpp:
Сообщение о баге в std::shared_mutex на Windows — На некоторых системах и под определёнными нагрузками SRWLock может вести себя некорректно.
GitHub Microsoft STL:
Issue #4448 в репозитории Microsoft STL.
На что же заменить?
Я часто встречаюсь с этой проблемой у клиентов, использующих ПО на Windows, при больших нагрузках и мощных серверах. Для компиляции используется VS 2022 со стандартной STL-реализаицей. Но на что можно быстро заменить, не потеряв в производительности?
boost::shared_mutex не походит из-за ограничения на 128 потоков (пруф);
folly не поддерживает 32-разрядные системы (пруф);
в POCO реализация, мягко говоря, не оптимальная (GitHub);
ACE_RW_Mutex Class просто не хочу.
kemsky
Есть ощущение, что это перевод, Issue #4448 закрыт 9 мая.
vma Автор
Закрыт, да. Но релиз фикса запланирован на Windows 12+. Выкатывать патчи для текущих версий ОС они не планируют, т.к. считают фикс "дестабилизирущим", об этом всём в том Issue и написано.
И нет, не перевод.
И это реальная проблема в продакшене, всё ещё не решённая.
Playa
В том issue написано, что STL не знает, когда фикс выпустится. Решение об этом принимает другая команда.