Почему эта функция 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")
}
Другие вопросы по тегам