@Xazzzi
Творю for fun and profit!

Особая магия с channels в golang?

Взгляните сюда, господа.
У меня тет 30 горутин соревнуются, которая из них первой отправит свой номер в канал.
Горутина-судья берет первое число из channel'я и закрывает его, т.к. свою задачу тот сделал, и номера от проигравших горутин нас не интересуют.
А теперь суть вопроса.
Во-первых, почему я не могу сделать канал буферизированным (с размером буфера равным 1)? Например, с целью сразу запустить в рутине-победителе что-то там важное, а не дожидаться, пока горутина-судья соизволит вытащить номер?
Во-вторых, что случится, если закрыть буферизированый канал до того, как считать из него все значения, успевшие туда попасть? Соберется ли такой chan сборщиком мусора при уходе его в out of scope? Что будет с обьектами внутри канала?
Да будет диспут.
  • Вопрос задан
  • 6540 просмотров
Решения вопроса 2
Tyranron
@Tyranron
Сначала отвечу на второй вопрос.
close() никакого отношения к scope не имеет вообще, он просто делает канал "закрытым", то есть таким, что при чтении значений из канала и записи из него возникает panic. Это всё. Соответственно...
что случится, если закрыть буферизированый канал до того, как считать из него все значения, успевшие туда попасть?
Канал закроется, при следующей попытке считать с него значение получите panic. Значения, что остались внутри, считайте потерянными, Вы их больше никак не получите.
UPD: Это не так! (см. конец поста и комментарии)
Соберется ли такой chan сборщиком мусора при уходе его в out of scope? Что будет с обьектами внутри канала?
Соберется он сборщиком мусора только тогда, когда на него никто больше не будет ссылаться (если он объявлен локально, то да, out of scope). Объекты внутри тоже соберутся, если на них больше никто не ссылается. Те, на которые еще ссылается кто-то, останутся.

Теперь замечания по Вашему примеру:
select {
case b <- number:
    fmt.Printf("Sent into b: %d\n", number)
default:
    fmt.Printf("b chan closed\n")
}
Этот кусок здорово дезинформирует. Во-первых select на запись c default секцией никоим образом не спасает от panic при записи в закрытый канал. Он всего лишь делает запись в канал всегда неблокируемой. Как только Вы таким select'ом попытаетесь записать в закрытый канал что-то, словите сразу панику. Потому правильно для восприятия это место выглядит следующим образом:
select {
case b <- number:
    fmt.Printf("Sent into b: %d\n", number)
default:
    fmt.Printf("Number %d just has been thrown away\n", number)
}
Если Вы сделаете канал a буферизированным, то тут Вам Ваши panic'и и полетят, потому что Вы пишете в закрытый канал.
Закономерный вопрос: почему тогда panic'и не летят при небуферизированном канале а?
Ответ: Вам просто везет =)
Во-первых, на playground'е runtime.GOMAXPROC=1 и runtime.NumCPU=1. То есть в реальности все дело крутится в одном потоке и параллельность тут псевдо.
Во-вторых, у меня локально (OS X) этот скрипт выкидывает panic'у после получения числа 25 даже при runtime.GOMAXPROC=1. Вам банально повезло, что внутренний планировщик горутин на playground'е ведет себя именно таки образом. При буферизированном канале он ведет себя немного иначе и Вы получаете закономерную panic'у сразу.

Если совсем на пальцах по первому вопросу, то:
При небуферизированном канале close(b) по каким-то соображения планировщика выполняется только после того как отработали обе горутины, если глянуть на вывод, то надпись "B:1" будет в самом конце. Потому то все и отрабатывает нормально, хотя это поведение совершенно не гарантированно логикой программы, наоборот, программа рассчитана на то, что close(b) выполнится сразу после того, как мы оттуда что-то считаем. Это и происходит, если канал a сделать буферизированым (надпись "B: 1" вылетает сразу), так как в этом случае планировщик меняет свои соображения, и мы получаем закономерную panic'у.

Дополнительно:
Канал b должен быть буферизированным, иначе, если горутина судья отработает быстрее чем главная горутина вообще начнет слушать канал, то все значения просто выкинутся. Эта проблема хорошо описана в этой статье на двух предпоследних абзацах.

UPD:
Я допустил ошибку, как верно указали в комментариях Виталий Яковенко и SilentFl, закрытый канал не выдает panic при чтении с него. Он разрешает считать все значения, которые в нем остались, после чего отдает "пустые" значения для свое типа, больше никогда не блокируя выполнение.
Закрытый канал panic'ует только при попытке отправить в него значение.
Ответ написан
@Xazzzi Автор вопроса
Творю for fun and profit!
Нашел решение в виде sync.Once:
play.golang.org/p/zH8umNJU9D
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
uvelichitel
@uvelichitel Куратор тега Go
habrahabr.ru/users/uvelichitel
Горутина-судья берет первое число из channel'я и закрывает его, т.к. свою задачу тот сделал, и номера от проигравших горутин нас не интересуют.
Идеоматично закрывать channel посылающей стороне.
Если посылка вам не нужна, но вы не хотите блокировать channel, то _=<-chan.
А теперь суть вопроса.
Во первых. Вы можете.
Во-вторых. Из закрытого channel можно дочитать все посылки и потом сколь угодно долго читать из него zero value. Это свойство и эксплуатируется для широковещательной посылки.
sync.Once безусловно корректное решение, но оно умаляет общность до одного призера. Можно обойтись только channel play.golang.org/p/T8k8_97Iqh
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы