Как горутина узнает о готовности данных в канале в Go
Что проверяют
Понимаете ли вы, что за оператором <- нет опроса в цикле: горутина засыпает, и её будит тот, кто отправил данные. Хороший ответ спускается до устройства канала в рантайме и до планировщика. Следом часто спрашивают про select с несколькими готовыми каналами и про закрытый канал.
Как отвечать
Канал в рантайме это структура hchan: мьютекс, кольцевой буфер (у буферизованного канала), индексы sendx и recvx и две очереди ожидающих, recvq и sendq. Ожидающая горутина лежит в очереди как sudog.
Чтение из канала идёт так:
- данные уже есть: в буфере лежит значение или в
sendqждёт отправитель, тогда горутина забирает значение сразу и не блокируется; - данных нет: горутина создаёт
sudog, встаёт вrecvqи вызываетgopark. Она переходит в ожидание, а поток ОС сразу берёт другую горутину, процессор не тратится; - пришёл отправитель: под мьютексом канала он видит ждущего получателя, копирует значение прямо в его стек, минуя буфер, и вызывает
goready. Получатель становится runnable и попадает в слотrunnextу P отправителя, то есть выполнится одним из следующих; - канал закрыли:
closeбудит всех ждущих получателей, они получают нулевое значение, а формаv, ok := <-chвозвращаетok == false.
У select без default механика та же, но сразу на несколько каналов: рантайм блокирует их в фиксированном порядке, опрашивает в случайном, и если готовых нет, ставит горутину в очереди всех каналов. Первый сработавший канал её будит, из остальных очередей она убирается. С default ожидания нет, это неблокирующая проверка.
Где спотыкаются
Говорят «блокируется, пока другой поток не отправит данные». Горутина не поток, и её блокировка не блокирует поток ОС: планировщик просто запускает следующую.
Считают, что select выбирает первый по тексту case. При нескольких готовых каналах выбор случайный, на порядок вывода в таком примере опираться нельзя.
Забывают про nil канал: чтение и запись в него блокируются навсегда. В select так выключают ветку, а вне его получают дедлок.