Канал имеет странное поведение, почему блокировать? - PullRequest
0 голосов
/ 15 января 2019

go версия go1.11.4 darwin / amd64

Был создан новый канал и программа, а содержимое старого канала было перенесено на новый канал через программу. Он не должен блокироваться, но после тестирования было обнаружено, что он заблокирован.

спасибо.

type waiter struct {
    ch1   chan struct{}
    ch2   <-chan time.Time
    limit int
    count int
}

func (w *waiter) recv1Block() chan struct{} {
    ch := make(chan struct{})
    go func() {
        for m := range w.ch1 {
            ch <- m
        }
    }()
    return ch
}

func (w *waiter) runBlock(wg *sync.WaitGroup) {
    defer wg.Done()

    i := 0
    for i < w.limit {
        select {
        case <-w.recv1Block():  **// why block here?**
            i++
        case <-w.recv2():
        }
    }
    w.count = i
}

почему recv1Block будет блоком.

1 Ответ

0 голосов
/ 15 января 2019

Каждый раз, когда вы звоните recv1Block(), он создает новый канал и запускает фоновую процедуру, которая пытается прочитать все данные с него. Поскольку вы вызываете его в цикле, у вас будет много вещей, которые будут пытаться использовать данные из канала; поскольку канал никогда не закрывается, все программы будут работать вечно.

В качестве упражнения вы можете попробовать изменить код так, чтобы он пропускал chan int вместо chan struct{}, и записывал серии последовательных чисел и распечатывал их по мере их получения. Такая последовательность действительна:

  1. run при вызове № 1 recv1Block().
  2. recv1Block() на GR # 1 порождает GR # 2 и возвращает канал № 2.
  3. run на блоках GR # 1, получающих на канале # 2.
  4. recv1Block() на GR # 2 читает 0 от w.c1.
  5. recv1Block() на GR # 2 записывает 0 в канал # 2 (где run на GR # 1 готов к чтению).
  6. recv1Block() на GR # 2 читает 1 от w.c1.
  7. recv1Block() на GR # 2 хочет записать 0 на канал # 2, но блокирует.
  8. run на GR # 1 просыпается и получает 0.
  9. run на вызовах GR # 1 recv1Block().
  10. recv1Block() на GR # 1 порождает GR # 3 и возвращает канал № 3.
  11. recv1Block() на GR # 3 читает 2 от w.c1.
  12. ...

Обратите внимание, что значение 1 в этой последовательности никогда нигде не будет записано, и на самом деле не осталось ничего, что могло бы его прочитать.

Простое решение здесь состоит в том, чтобы не вызывать функцию создания канала в цикле:

func (w *waiter) runBlock(wg *sync.WaitGroup) {
    defer wg.Done()
    ch1 := w.recv1Block()
    ch2 := w.recv2()
    for {
        select {
        case _, ok := <-ch1:
            if !ok {
                return
            }
            w.count++
        case <-ch2:
    }
}

Это также стандартная практика закрывать каналы, когда вы закончите с ними. Это завершит цикл for ... range ch и будет выглядеть как читаемое для оператора select. В вашей функции генератора верхнего уровня:

for i := 0; i < w.limit; i++ {
    w.ch1 <- struct{}{}
}
close(w.ch1)

А в вашей функции "копировать канал":

func (w *waiter) recv1Block() chan struct{} {
    ch := make(chan struct{})
    go func() {
        for m := range w.ch1 {
            ch <- m
        }
        close(ch)
    }()
    return ch
}

Это также означает, что вам не нужно запускать основной цикл «мертвым расчетом», ожидая, что он произведет ровно 100 элементов, а затем остановится; Вы можете остановиться, когда выходит его канал. Цикл потребления, который я показываю выше, делает это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...