Когда для нескольких процедур назначается сигнал отмены, чаще всего это делается путем закрытия канала, а не отправки значения в канал. Прием из закрытого канала может начаться немедленно, независимо от того, сколько это выполнено. Значение, отправленное по каналу, может быть получено не более одного раза, поэтому он не подходит для того, чтобы сигнализировать множеству процедур со значением. Спецификация: Оператор получения:
Операция приема на закрытом канале всегда может выполняться немедленно, давая нулевое значение типа элемента после получения любых ранее отправленных значений.
Теперь, если вы закроете канал quit
, это не гарантирует, что ваша функция вернется немедленно.
Сначала вы возвращаетесь к левому ребенку, не проверяя quit
, который будет делать то же самое (пока не будет достигнут nil
левый ребенок).
Во-вторых, если значение может быть отправлено на ch
, тогда оба случая готовы и, таким образом, select
выбирает один из них случайным образом, что может быть или не быть случаем quit
. Подробнее см. Как работает выбор, когда задействованы несколько каналов?
Если вы хотите избежать этого, вы должны добавить неблокирующую проверку quit
в качестве первой вещи в вашей функции:
func walkImpl(t *tree.Tree, ch, quit chan int) {
select {
case <-quit:
return
default: // This empty default makes it a non-blocking check
}
if t == nil {
return
}
walkImpl(t.Left, ch, quit)
select {
case ch <- t.Value:
// Value successfully sent.
case <-quit:
return
}
walkImpl(t.Right, ch, quit)
}
Теперь можно было бы спросить, нужен ли нам еще случай quit
во втором select
, поскольку мы уже проверили его первым делом в walkImpl()
. Ответ заключается в том, что вы также должны сохранить это, потому что, если отправка на ch
заблокирует (например, потребитель будет отключен, когда quit
будет закрыт), эта операция отправки может заблокироваться навсегда. Таким образом (когда quit
закрыто) гарантируется, что функция вернется.