Супер. У меня всего два c половиной вопроса
1) Каналы изнутри сами построены на мьютексах?
2) Когда мне использовать мьютекс, а когда буфферизованный канал размером 1?
Тут мне кажется, что разницы между
mutex.Lock()
xxx
mutex.Unlock()
и
<- chan
xxx
1 -> chan
нет, но второй вариант безопаснее. То есть каналы не сложнее мьютекса в самом коде, но безопаснее.
2.5) рантайм Go отлично шедулит горутины когда они засыпают на ожидании данных из канала — делает ли он так же и для мьютексов?
Буферизированный канал, по сути, семафор. На счет безопасности, не вижу разницы. Разве что код с мьютексом будет куда очевиднее и скорее всего быстрее.
1. Каналы хитрые. Некоторые операции делаются с оптимистичными блокировками, некоторые чисто на мьютексах.
2. Каналы нужно использовать по-другому. Это не слепая замена мьютексам. Надо на каждый разделяемый ресурс запускать отдельную горутину, которой присылать команды на изменение данных и запросы на чтение. Эта горутина просто исполняет все, о чем её просят, гарантированно последовательно.
По сути ваших примеров кода — аналог мьютекса так можно сделать. За исключением всяких крайних случаев типа двойного вызова Unlock.
1) Каналы используют блокировки, но технически они не являются sync.Mutex блокировкой. Будет точнее сказать, что блокировки для реализации каналов, мютексов, семафоров, рлоков и т.д. используют атомарные процессорные инструкции (типа CaS).
2) Разницы нет в достигнутом результате, но семантически первый вариант корректнее. Откуда мне знать, что у Вас используется именно буферизированный канал размером 1? А когда я вижу mutex.Lock() — то я знаю что это ексклюзивная блокировка.
2.5) «отлично» это слишком сильно сказано. Неплохо — это факт. Но накладные расходы на шедулинг каналов все ещё не дают признать их самым предпочтительным средством разделения доступа. А мютексы рантайм тоже шедулит, если встречает лок.
Спасибо, я почему-то как раз считал именно наоборот, поэтому и думал, что каналы безопаснее. Спасибо. Это ключевой момент, как мне кажется, должен быть упомянут в статье.
defer unlock даёт те же гарантии, что и lock_guard — при выходе из функции (любом, даже через panic) мьютекс освободится. Я бы сказал, что defer — это оригинальная идея, которая позволяет избежать использования guard'ов и предоставить аналогичную функциональность вообще для любых объектов. Открыли соединение — defer закрыть. Получили блокировку — defer отпустить. Для случаев с более сложной логикой лучше не танцевать с мьютексами, см. статью.
Кто ж блокировками заставляет пользователя заниматься? Пользователю снаружи виден метод, а его реализация уже делает все необходимые блокировки. И если она это делает, то делает lock() и defer unlock(). Сразу. Рядом. Без шансов забыть unlock. Об этом речь идёт.
Вообще говоря, lock_guard дает гарантию блокировки мьютекса при создании на стеке и автоматической разблокировки при выходе из области видимости (а не из функции). При любом выходе, вызывающем раскрутку стека.
Что хорошего в этом комментарии? Чем он полезен для посетителей?
Где нормальное аргументирование своих слов? А не только «разнос» без всякого разбора в ситуации.
> Обратите внимание в этом коде, что для каждого не-экспортированного метода есть аналогичный экспортированный. Эти методы работают как публичный API, и заботятся о блокировках на этом уровне. Далее они вызывают неэкспортированные методы, которые вообще не заботятся о блокировках. Это гарантирует, что все вызовы ваших методов извне будут блокироваться лишь раз и лишены проблемы рекурсивной блокировки.
Копипаста и никем не проверяемые конвенции. Славно. В том же D достаточно объявить класс как synchronized и все публичные методы автоматом будут завёрнуты в локи, а прямой доступ к полям вообще запрещён извне.
Знаете, мне этот го напоминает прыщавого подростка — весь такой из себя уверенный в своей исключительности, знает как надо, учиться у взрослых ничему не хочет, зато понтуется сколько он намайнкрафтил словно великим достижением :-D
Танцы с мьютексами в Go