Отладьте эту fan-in-склейку каналов: range merge(...) уходит в deadlock. Найдите обе ошибки.
Этот merge сводит несколько каналов в один out, а отдельная goroutine должна закрыть out, когда все продюсеры завершатся. range по merge(...) уходит в deadlock. Здесь две ошибки.
func merge(chs ...chan int) chan int {
out := make(chan int)
wg := &sync.WaitGroup{}
for _, c := range chs {
wg.Add(1)
go func() {
defer wg.Done()
for val := range c {
out <- val
}
}()
}
go func() {
wg.Wait()
close(out)
}
return out
}
func main() {
ch1 := startProducerA()
ch2 := startProducerB()
for el := range merge(ch1, ch2) {
println(el)
}
}
Найдите и исправьте ошибки.
Две ошибки. Закрывающая goroutine записана как go func(){ wg.Wait(); close(out) } без завершающих (), поэтому она объявлена, но не запущена — out никогда не закрывается и range merge(...) уходит в deadlock. Вторая: замыкание worker захватывает переменную цикла c, поэтому до 1.22 все goroutine читают один и тот же последний channel. Исправление: вызвать закрытие через () и передать c аргументом (или переобъявить).
- ✗Читать
go func(){...}как запущенную goroutine, хотя без()это лишь объявленное, никогда не вызванное значение - ✗Считать, что до 1.22 переменная цикла своя на итерацию, поэтому захваченный
c— последний channel для каждого worker - ✗Винить в deadlock гонку
closeс отправкой вместо того, чтоoutникогда не закрывается
- →Почему переменная цикла на итерацию в Go 1.22 убирает необходимость передавать
cаргументом? - →Как
go vetили race detector помогут поймать захват переменной цикла до runtime?
Код с ошибками
func merge(chs ...chan int) chan int {
out := make(chan int)
wg := &sync.WaitGroup{}
for _, c := range chs {
wg.Add(1)
go func() {
defer wg.Done()
for val := range c { // BUG 2: до 1.22 захватывает переменную цикла c
out <- val
}
}()
}
go func() {
wg.Wait()
close(out)
} // BUG 1: нет завершающих () — goroutine объявлена, но не запущена
return out
}
func main() {
ch1 := startProducerA()
ch2 := startProducerB()
for el := range merge(ch1, ch2) { // deadlock: out никогда не закрывается
println(el)
}
}
Что не так
Ошибка 1 — закрытие не запущено. go func(){ wg.Wait(); close(out) } без () — это объявление литерала функции, переданное go, но сама функция не вызывается. Закрывающая goroutine не стартует, out никогда не закрывается, и range merge(...) в main блокируется навсегда после исчерпания значений. Это deadlock, а не паника.
Ошибка 2 — захват переменной цикла. До Go 1.22 переменная c одна на весь цикл. Все worker-goroutine замыкаются на одну и ту же c, и к моменту их запуска она держит последний channel. Часть продюсеров не читается; результат недетерминирован.
Исправление
func merge(chs ...chan int) chan int {
out := make(chan int)
wg := &sync.WaitGroup{}
for _, c := range chs {
wg.Add(1)
go func(c chan int) { // передаём c аргументом (до 1.22)
defer wg.Done()
for val := range c {
out <- val
}
}(c)
}
go func() {
wg.Wait()
close(out)
}() // запускаем закрывающую goroutine
return out
}
В Go 1.22+ переменная цикла своя на каждую итерацию, поэтому передавать c аргументом уже не нужно — но () у закрывающей goroutine обязательны в любой версии.