Оператор select
без регистра default
является блокирующим до тех пор, пока не будет выполнено чтение или запись хотя бы в одном из операторов case
. Соответственно, ваш select
будет блокироваться до тех пор, пока не будет возможно чтение с канала quit
(либо значения, либо нулевого значения, если канал закрыт). Спецификация языка предоставляет конкретное описание этого поведения, в частности:
Если одно или несколько сообщений [выраженных в case
заявлениях] могут продолжаться, то одно из них, которое может продолжаться, выбирается с помощью равномерного псевдослучайного выбора. В противном случае, если есть случай по умолчанию, этот случай выбирается. Если регистр по умолчанию отсутствует, оператор SELECT блокируется до тех пор, пока не будет продолжено хотя бы одно из сообщений.
Другие проблемы с кодом
Внимание! break
относится к выражению select
Однако, даже если вы закрыли канал quit
, чтобы сигнализировать об отключении вашей программы, ваша реализация, скорее всего, не даст желаемого эффекта. Вызов (без метки) break
прекратит выполнение самого внутреннего for
, select
или switch
оператора внутри функции. В этом случае оператор select
прервется, и цикл for
снова будет выполнен. Если quit
был закрыт, он будет работать вечно, пока что-то еще не остановит программу, в противном случае он снова заблокируется в select
( Пример игровой площадки )
Закрытие quit
(вероятно) не сразу остановит программу
Как отмечено в комментариях , вызов time.Sleep
блокируется на секунду на каждой итерации цикла, поэтому любая попытка остановить программу закрытием quit
будет отложена на срок до примерно за секунду до того, как программа проверяет quit
и убегает. Вряд ли этот период ожидания должен завершиться полностью до остановки программы.
Более идиоматический Go будет блокировать в операторе select
при получении из двух каналов:
- Канал
quit
- Канал, возвращаемый
time.After
- этот вызов является абстракцией вокруг Timer
, который спит в течение определенного периода времени, а затем записывает значение в предоставленный канал.
Разрешение
Решение с минимальными изменениями
Решение, чтобы исправить вашу непосредственную проблему с минимальными изменениями в вашем коде:
- Сделайте чтение из
quit
неблокирующим, добавив регистр по умолчанию в оператор select
.
- Обеспечение выполнения процедуры на самом деле возвращается, когда чтение из
quit
завершается успешно:
- Пометьте петлю
for
и используйте помеченный вызов break
; или
return
из функции f1
, когда пришло время выйти (предпочтительно)
В зависимости от ваших обстоятельств, вам может показаться более идиоматичным Перейти к использованию context.Context
для сигнализации о завершении и к использованию sync.WaitGroup
для ожидания выполнения процедуры закончить перед возвращением с main
.
package main
import (
"fmt"
"time"
)
func f1(quit chan bool) {
go func() {
for {
println("f1 is working...")
time.Sleep(1 * time.Second)
select {
case <-quit:
fmt.Println("stopping")
return
default:
}
}
}()
}
func main() {
quit := make(chan bool)
f1(quit)
time.Sleep(4 * time.Second)
close(quit)
time.Sleep(4 * time.Second)
}
Рабочий пример
(Примечание: я добавил дополнительный вызов time.Sleep
в ваш метод main
, чтобы избежать его возврата сразу после вызова на close
и завершения программы.)
Исправление проблемы блокировки сна
Чтобы устранить дополнительную проблему, связанную с блокировкой сна, предотвращающей немедленный выход, переместите сон в таймер в блоке select
. Изменение вашего for
цикла согласно этому примеру игровой площадки из комментариев делает именно это:
for {
println("f1 is working...")
select {
case <-quit:
println("stopping f1")
return
case <-time.After(1 * time.Second):
// repeats loop
}
}
Связанная литература