Что происходит, когда вы прерываете оператор for с помощью канала диапазона - PullRequest
0 голосов
/ 03 августа 2020

Я следую этому коду, чтобы получить ленивый диапазон чисел с каналами

// iterator
func iterator(n int, c chan int) {
    for i := 0; i < n; i++ {
        c <- i
    }
    close(c)
    fmt.Println("iterator End")
}

c := make(chan int)
go iterator(5, c)
for i := range c {
    fmt.Println(i)
}

Это будет напечатано, как ожидалось

0
1
2
3
4
fmt.Println("iterator End")

Но что случилось когда я нарушаю на l oop вот так

c := make(chan int)
go getNumbers(5, c)
for i := range c {
    if i == 2 {
        break
    }
    fmt.Println(i)
}

Кажется, что горутина заблокирована, потому что никогда не печатает iterator End (я также пытаюсь спать основной поток). Мне интересно, как справиться с этим сценарием? Мне нужно было использовать select, чтобы решить эту проблему? Есть какой-нибудь безопасный способ проверить, был ли диапазон break , и остановить for-l oop в итераторе?

1 Ответ

3 голосов
/ 03 августа 2020

Если горутина записывает в небуферизованный канал и никакая другая горутина не читает из этого канала - тогда запись будет заблокирована навсегда. Это вызовет утечку горутины. Это то, что вы испытываете.

Если у вас есть горутина «производителя», которая записывает в канал, вам нужен способ дать ей сигнал остановиться. Закрытие канала здесь не критично - каналы собираются мусором, когда они go выходят за рамки. Заблокированная горутина (которая никогда не разблокируется) считается утечкой, поскольку она никогда не будет восстановлена, поэтому вам действительно нужно, чтобы горутина завершилась.

Вы можете сигнализировать о намерении выйти разными способами - два самых популярных будучи:

Сигнал: готовый канал

func iterator(n int, c chan int, done <-chan struct{}) {
    for i := 0; i < n; i++ {
        select {
        case c <- i:
        case <-done:
            break
        }
    }
    close(c)
    fmt.Println("iterator End")
}

читатель goroutine:

c := make(chan int)
done := make(chan struct{})
go iterator(5, c, done)
for i := range c {
    if i == 2 {
        break
    }
    fmt.Println(i)
}
close(done) // signal writer goroutine to quit

Сигнал: context.Context

func iterator(ctx context.Context, n int, c chan int) {
        defer close(c)
        defer fmt.Println("iterator End")

        for i := 0; i < n; i++ {
                select {
                case c <- i:
                case <-ctx.Done():
                        fmt.Println("canceled. Reason:", ctx.Err())
                        return
                }
        }
}

читать горутину:

func run(ctx context.Context) {
        ctx, cancel := context.WithCancel(ctx)
        defer cancel()  // call this regardless - avoid context leaks - but signals producer your intent to stop
        c := make(chan int)
        go iterator(ctx, 5, c)
        for i := range c {
                if i == 2 {
                        break
                }
                fmt.Println(i)
        }
}

https://play.golang.org/p/4-fDyCurB7t

...