Язык Go — кратко. Часть 6. Каналы

Каналы крайне полезная штука и используются для передачи данных между рутинами, а также для синхронизации. Альтернативный способ — локи. Но каналы — идейно правильный. 😉

Каналы могут быть синхронными и асинхронными, двунаправленными и однонаправленными.

Синхронный канал приводит к остановке кода при чтении, пока в канал не отправят данные, и при записи, пока из канала не прочтут уже имеющееся в нём.

Асинхронный канал — канал с буфером, который блокирует код рутины только если весь его буфер занят. Отличие от синхронного в том, что при создании мы указываем размер буфера.

То есть, синхронный канал — это канал с размером буфера = 0.

Разберём подробнее принцип работы.

а) программа встречает инструкцию чтения из канала. Если в канал ещё никто ничего не отправлял или всё отправленное из него уже забрали, программа блокируется.

б) программа встречает инструкцию записи в канал. Если буфер канала исчерпан, программа блокируется.

Почему тут происходят блокировки в обоих случаях? Потому что канал синхронный. Буфер нулевой — некуда временно сохранить значение. В зависимости от наших целей, можно либо сделать канал с ненулевым буфером, либо (а обычно «и») запустить рутины, которые будут читать/отправлять данные.

Как вы понимаете, в примере выше буферизированный канал нас не спасёт, если мы сразу же в цикле в него попробуем отправить данных больше, чем размер буфера. Мы просто отправим столько, сколько вместится в буфер и затем дедлок. Поначалу это кажется не очень понятным, но когда будете писать параллельный код, довольно быстро набьёте пару шишек и всё усвоите. 😉

В примере выше канал двунаправленный. В него можно отправить данные и из него можно прочесть данные.

Простенький пример:

Пример вывода:

Сон в конце добавлен только для того, чтобы упростить код и наверняка увидеть весь вывод. Правильно было не спать в надежде, что рутины все успеют завершиться, а непосредственно ждать их завершения. Для этого можно было бы использовать sync.WaitGroup или те же каналы (ещё один создать, в который рутины по завершении бы писали; или даже два 😉 ).

В целом пример крайне прост (и крайне надуман). Мы создаём два канала. Один для отправки имён в рутину, которая сообщает в консоль, что человек зачекинился. Второй канал используется для подсчёта чекинов. Естественно, необходимости в нём нет, так можно чуть лучше представить общение между рутинами. Не для всех очевидно, что main-функция программы — это тоже, по сути, рутина.

Итак, создали два канала. Запустили две рутины. И стали пихать в канал имён эти самые имена. И радоваться тому, как всё завораживающе работает.

Да, кстати. Не забывайте выносить мусор закрывать каналы. 😉

for … range по каналу бегает до тех пор, пока канал открыт.

А что делать, если у нас не один канал и мы желаем одновременно их проверять на доступность данных?

В этом случае нам помогает select.

Первая функция иллюстрирует возможность работы одновременно с несколькими каналами. Если оба кейса доступны для выполнения, какой будет выполнен, выбирается случайно.

 

Leave a Reply

Ваш e-mail не будет опубликован. Обязательные поля помечены *