В следующем примере подпрограмма go перекачивает значения в небуферизованный канал, и основная функция выполняет итерацию по нему.
package main
import (
"fmt"
"strconv"
)
var chanStr chan string
func main() {
go pump()
fmt.Println("iterating ...")
for val := range chanStr {
fmt.Printf("fetched val: %s from channel\n", val)
}
}
func pump() {
defer close(chanStr)
chanStr = make(chan string)
for i := 1; i <= 5; i++ {
fmt.Printf("pumping seq %d into channel\n", i)
chanStr <- "val" + strconv.Itoa(i)
}
//close(chanStr)
}
Функция паникует со следующим выводом:
iterating ...
pumping seq 1 into channel
pumping seq 2 into channel
fetched val: val1 from channel
......
fetched val: val4 from channel
pumping seq 5 into channel
panic: close of nil channel
goroutine 5 [running]:
main.pump()
C:/personal/gospace/go-rules/test.go:26 +0x1a6
created by main.main
C:/personal/gospace/go-rules/test.go:11 +0x4e
Однако, если я прокомментирую оператор defer и закрою сразу после цикла for в процедуре pump
, получатель не паникует. В чем разница в обоих случаях? Похоже, отсрочка закрывает канал до получения значения, но обычное закрытие ждет.
Также, когда я строил с использованием детектора гонки, даже врегулярное закрытие сигнализирует о потенциальном состоянии гонки (я не могу каждый раз воссоздавать гонку). Означает ли это, что оба эти способа не подходят для грациозного закрытия канала?
ОБНОВЛЕНИЕ: Для всех комментирующих я знаю, в чем проблема. Я должен создать канал в первой строке функции main()
. Однако я работаю на Windows с Go1.12, и я заметил это поведение. Очевидно, я не подделал вывод. Я последовательно воссоздаю панику, используя оператор defer, и даже не однажды паника возникла, когда я закрыл канал сразу после цикла for в pump()