Синхронизация состояний с использованием Mutex vs Channel в Go
Исходное сообщение Ссылка на сайт
Это одна из моих статей, которая заставила меня изучить весь Google в поисках того, какие методы лучше всего подходят для того, чтобы синхронизация работала хорошо в GoLang. Все это началось, когда я начал создавать свой собственный пакет GoLang, Go-Log. Это пакет ведения журнала, который предоставляет утилиту поверх обычного пакета журнала Go с такими функциями, как — пометка журналов в вариант отладки и ошибки, добавление/удаление временной метки в журналах, а также получение сведений о вызывающей функции в журналах. Тем не менее, была большая необходимость сделать эту регистрацию потокобезопасный или вы можете было необходимость синхронизации поскольку, когда на сервер приходят миллионы запросов, логи нужно синхронизировать с меньшей задержкой.
Просматривая статьи, вопросы и ответы StackOverflow, видео в идиоматическом стиле Go, я наткнулся на два метода:
- Координация с каналами, которые уже являются потокобезопасными
- Координация использования Mutex в общей памяти
Самая распространенная ошибка Go-разработчика?
Прежде чем приступить к обсуждению того, какой метод выбрать, вам всем нужно знать распространенную ошибку Go-разработчика, которую вы все совершаете. Я знаю, что вы все фантазируете о шаблоне параллелизма, который заставляет вас чрезмерно использовать суперсилу Go — каналы и горутины, что в конце концов становится анти-паттерном. Я не говорю, что каналы плохие или их нельзя использовать для синхронизации, но я говорю о чрезмерном использовании. (используя его там, где нет необходимости) это определенно не тот путь, которому вы должны следовать.
Дэйв Чейни, участник проекта с открытым кодом и язык программирования Go, однажды в интервью сказал: «Я пытался использовать каналы для всего, если вы хотите говорить о худшем коде».
В официальной документации Go говорится, что «распространенной ошибкой новичков в Go является чрезмерное использование каналов и горутин только потому, что это возможно и/или потому, что это весело».
Теперь, предполагая, что у вас нет никаких предварительных представлений о горутинах, каналах или мьютексах, давайте посмотрим на разницу между двумя подходами, упомянутыми ранее, о том, как выполнять синхронизацию.
Связь с использованием каналов для достижения синхронизации
каналы — это каналы, которые соединяют параллельные горутины. Вы можете отправлять значения в каналы из одной горутины и получать эти значения в другую горутину.
Каналы лучше всего подходят в таких случаях, как передача прав собственности на данные, распределение единиц работы и передача асинхронных результатов.
Давайте воспользуемся этими каналами для синхронизации моего пакета Go Lang (цель состоит в том, чтобы журналы работали синхронно и потокобезопасно). Ниже приведен фрагмент кода из моего пакета, который я написал ранее.
Я использовал базовую коммуникацию по каналам для синхронизации нескольких горутин, что может произойти, когда журналы разъединяются миллион раз. Лучшее в каналах то, что они имеют встроенную безопасность потоков и предотвращают состояние гонки.
Если внимательно посмотреть на мой случай и спросить, была ли для этого определенная необходимость в каналах? Ответ НЕТ. Каналы — это концепция высокого уровня в Go, которая где-то внутри использует только Mutex. Каналы Go привлекательны, поскольку они обеспечивают встроенную безопасность потоков и поощряют однопоточный доступ к общим критически важным ресурсам. Каналы, однако, несут потери производительности по сравнению с мьютексами. Использование мьютексов полезно, когда вам просто нужны блокировки для нескольких общих ресурсов. Не бойтесь использовать sync.Mutex
если это лучше всего подходит для вашей проблемы.
Как я использовал Mutex для синхронного логирования?
Мьютекс (блокировка взаимного исключения) — бесценный ресурс при синхронизации состояния между несколькими горутинами, но я считаю, что его использование несколько загадочно для новых разработчиков Go. Это довольно легко использовать!
Вот фрагмент кода из моего пакета, Go-Log
Я использовал mutex.Lock() и mutex.Unlock() для создания синхронной блокировки общего ресурса. А для управления несколькими логами я каждый раз создаю горутину и добавляю их в sync.WaitGroup. Это важный примитив синхронизации. Это позволяет взаимодействующим горутинам коллективно ожидать порогового события, прежде чем снова продолжить независимо друг от друга.
В нашем случае этот подход гораздо лучше и быстрее! Мы сократили ненужные накладные расходы. Однако, если вы когда-нибудь обнаружите, что ваши правила блокировки sync.Mutex становятся слишком сложными, спросите себя, может ли использование каналов быть проще.
Посмотрите исходный код моего пакета, чтобы узнать, как я реализовал мьютексы для достижения синхронизации —
Дополнительные ресурсы
Найди меня!
Твиттер: https://twitter.com/DuaYashish
НаставникКруиз: