Concurrency, Synchronization and Consistency. Пост № 21. RWMutex vs Mutex.
Ранее я написал посты про оба вида мьютексов, про внутренности и особенности. Настало время их сравнить.
Перед тем как что-то сравнивать нужны ориентиры. Нам как разработчикам важно:
- Чтобы код был простым.
- Чтобы код был быстрым.
- Чтобы код потреблял как можно меньше ресурсов.
🔵Mutex
🧠Простота. Здесь однозначно +. Внутренняя структура проще + публичное API для разработчика очевидно (Lock / Unlock).
🚀Скорость. Здесь есть над чем порассуждать.
➕ Внутри Mutex уже есть оптимизации (Fast / Slow path / Futex). Если код написан хорошо то мьютекса будет достаточно для эффективной работы.
➕Масштабируемость. Если в программе увеличить количество потоков и добавить ресурса то код будет примерно линейно масштабироваться (для операций чтения, для операций записи нужно будет синхронизировать кеши ядер CPU).
➖Нельзя распараллелить thread-safe операции чтения. Mutex это "эксклюзивное владение" ресурсом.
➖Как следствие - рост latency если критическая секция кода долгая. В случае короткой секции горутины / потоки не засыпают, а активно ждут своей очереди. Если не дождались - планировщик усыпит горутину, чтобы выделить ресурсы кому-то еще. Все это накладные расходы влияющие не столько на ресурсы сколько на время работы программы.
➖Помимо этого - рост latency если горутин / потоков много. Даже если критическая секция короткая потоки и горутины будут усыпляться планировщиком потому что на всех не хватает ядер. Накладные расходы на пробуждение / усыпление / взаимодействие с ОС.
💰Ресурсы. В худшем случае наши горутины и потоки спят. Ресурсы могут потреблять другие потоки и программы. В остальном mutex полагается на рантайм (языка или ос).
🔵RWMutex
🧠Простота. Тут однозначно минус. RWMutex это умная обертка над обычным мьютексом. Добавляется несколько atomic переменных чтобы в read cценарии избавиться от эксклюзивного владения и параллелить по ядрам чтения.
🚀Скорость. Здесь у RWMutex есть потенциал уделать обычный Mutex. В read-heavy сценариях горутины / потоки практически никогда не будут засыпать и блокироваться. А значит программа будет быстрее.
Также при добавлении ядер и потоков будет выше параллелизм -> еще ниже latency. Количество читающих горутин может исчисляться тысячами и они не будут блокироваться на мьютексе если хотят что-то прочитать.
Но - это только теория. На практике RWMutex может сделать хуже. Каждый вызов функций RWMutex изменяет atomic переменные. И чем больше у нас читателей / потоков и как следствие ядер CPU тем больше времени будет уходить на синхронизацию кешей процессора. Обычный Mutex лишен этого недостатка.
В Go даже есть issue с обсуждением этой проблемы
💰Ресурсы. Как упомянул ранее RWMutex потребляет больше чем обычный mutex потому что внутри него больше логики и данных. Это цена которую мы платим. Чем больше читателей и ядер тем больше "набегает" .
🔵Выводы. Что же делать?
№1 - Всегда начинать с Mutex. Он прост, адаптируется под изменение конфигурации (потоки / ядра). Если код написан правильно и критические секции короткие программа будет достаточно быстрой и эффективной.
№2 - Помнить про трейдофф latency vs throughput.
- Mutex - это low cpu / high latency.
- RWMutex - high cpu / low latency (до поры до времени).
№3 - Помнить о том что RWMutex имеет bottleneck
RWMutex спасает лишь до поры до времени. Однажды он станет вредителем а не помощником. Не упустите этот момент.
№4 - Метрики, профили, бенчмарки это наше всё.
Надеюсь мне удалось показать, что RWMutex очень специфичная вещь с кучей особенностей, и его сложно советовать по умолчанию. Внедрять его без понимания последствий может быть чревато ухудшением производительности.
Чтобы это понимание приобрести нужно время на сбор профилей, бенчмарки, и нагрузочные тесты. Они помогут понять будет ли иметь смысл внедрение RWMutex.
Преждевременная оптимизация - зло, а преждевременная оптимизация без метрик - абсолютное зло😈
Спасибо, что читали, буду рад вашей обратной связи и реакциям!
📖 Оглавление
Post #435
1.35K
Евгений Козлов пишет про IT Concurrency, Synchronization and Consistency. Пост № 20. Масштабируемость Read-Write блокировки. False Sharing. В прошлом посте мы с вами разобрались с основными моментами RW примитивов. Как и обещал - переходим к практике. У RW блокировок в классической…
- 👍 9
- 🔥 5
- ❤ 2