Почему эта функция golang _не_ работает вечно?
Я хотел попробовать тест FizzBuzz ( Почему программисты не могут программировать) и использовал Go. Это в основном циклы от 1 до 100 и печать "Fizz", когда счетчик цикла делится на 3, "Buzz", когда делится на 5, "FizzBuzz", когда делится на оба, а в противном случае просто печатать число.
Сделав это итеративно и рекурсивно, я хотел сделать это одновременно (или используя каналы). Я придумал следующий код, который, к моему удивлению, сработал:
func fizzbuzzconc() {
// Channels for communication
fizzchan := make(chan int)
buzzchan := make(chan int)
fizzbuzzchan := make(chan int)
nonechan := make(chan int)
// Start go routine to calculate fizzbuzz challenge
go func() {
for i := 1; i <= 100; i++ {
if i % 3 == 0 && i % 5 == 0 {
fizzbuzzchan <- i
} else if i % 3 == 0 {
fizzchan <- i
} else if i % 5 == 0 {
buzzchan <- i
} else {
nonechan <- i
}
}
}()
// When or how does this for loop end?
for {
select {
case i := <-fizzchan:
fmt.Println(i, "Fizz")
case i := <-buzzchan:
fmt.Println(i, "Buzz")
case i := <-fizzbuzzchan:
fmt.Println(i, "FizzBuzz")
case i := <-nonechan:
fmt.Println(i, i)
}
}
}
Я не могу понять, как и почему цикл for останавливается. Нет условия разрыва или оператора возврата. Почему он в конце концов заканчивает работу?
3 ответа
Это не очень хорошо работает.
Через некоторое время происходит то, что происходит истощение из-за оставшейся рутины ожидания в ожидании канала, где не будет проталкивать горутин. Так что у вас есть тупик (фатальная ошибка, завершающая программу), а не чистый конец.
В итоге это "работает", потому что движок go достаточно умен, чтобы обнаружить мертвую блокировку.
@OneOfOne упомянул метод sync.WaitGroup, который, я думаю, больше всего соответствует тому, как вы будете делать это в Go. Учитывая, что подпрограммы очень дешевы, и проблему можно решить параллельно, мы можем создать подпрограмму go для каждого входа и отправить результаты по буферизованному каналу.
//Size to check
size := 100
results := make(chan Result, size)
// Create the WaitGroup and set the latch size.
var wg sync.WaitGroup
wg.Add(size)
// Create a goroutine for each parallel operation
for i := 1; i <= size; i++ {
i := i //bind value into closure
go func() {
results <- fizzbuzz(i)
wg.Done() //release a latch
}()
}
//wait for all the goroutines to finish.
wg.Wait()
//close the channel so we can exit the below for loop.
close(results)
//Range over the results and exit once the channel has closed.
for x := range results {
fmt.Printf("i: %d result: %s\n", x.Nr, x.Val)
}
Код детской площадки: http://play.golang.org/p/80UafMax7M
@dystroy очень хорошо ответил на ваш вопрос, но вот как вы можете исправить свой код.
Один из способов аккуратного выхода - использовать канал выхода, который мы сигнализируем, закрывая его. (Сигнализация при закрытии канала полезна, потому что на нем одновременно могут прослушивать более одной подпрограммы).
Есть и другие способы сделать это - если у вас есть только один выходной канал, то вы range
над ним, чтобы прочитать результат и close
это когда ты закончил. Вы можете легко переписать это, чтобы работать таким образом.
Вы можете использовать sync.Waitgroup
чтобы убедиться, что рутина рутины также закончилась.
func main() {
// Channels for communication
fizzchan := make(chan int)
buzzchan := make(chan int)
fizzbuzzchan := make(chan int)
nonechan := make(chan int)
quit := make(chan struct{})
// Start go routine to calculate fizzbuzz challenge
go func() {
for i := 1; i <= 100; i++ {
if i%3 == 0 && i%5 == 0 {
fizzbuzzchan <- i
} else if i%3 == 0 {
fizzchan <- i
} else if i%5 == 0 {
buzzchan <- i
} else {
nonechan <- i
}
}
close(quit)
}()
// When or how does this for loop end?
OUTER:
for {
select {
case i := <-fizzchan:
fmt.Println(i, "Fizz")
case i := <-buzzchan:
fmt.Println(i, "Buzz")
case i := <-fizzbuzzchan:
fmt.Println(i, "FizzBuzz")
case i := <-nonechan:
fmt.Println(i, i)
case <-quit:
break OUTER
}
}
fmt.Println("All done")
}