Выход из цикла, когда ни один канал в выбранной группе не получает сигнал в указанное время - PullRequest
0 голосов
/ 09 ноября 2018

Как мне выйти из идиоматического цикла Go for, содержащего оператор select, если и только если я не получаю сигналов ни по одному из каналов, которые прослушивает мой оператор select, в течение определенного периода времени.

Позвольте мне дополнить вопрос примером.

Установка:

  1. Допустим, у меня есть канал var listenCh <-chan string, который я слушаю.
  2. Предположим, что какая-то другая подпрограмма go (не контролируемая нами) отправляет разные строки на этом канале.
  3. Я выполняю некоторую обработку с заданной строкой, а затем слушаю следующую строку в listenCh.

Требования:

Я хочу подождать максимум 10 секунд (точность не критична) между двумя последовательными сигналами на listenCh, прежде чем я завершу свои операции (окончательно прерваю цикл).

Код заглушки:

func doingSomething(listenCh <-chan string) {
  var mystr string
  for {
    select {
      case mystr <-listenCh:
        //dosomething
      case /*more than 10 seconds since last signal on listenCh*/:
        return
    }
  }
}

Как бы я выполнил свое требование наиболее эффективным способом?

Обычная техника выхода из канала с time.After(time.Duration), похоже, не сбрасывается после одного цикла, и, следовательно, вся программа закрывается через 10 секунд, даже если существует непрерывный поток значений.

Я нахожу варианты вопроса (но не то, что я хочу) на SO, но ни один из тех, что я видел, не отвечает моему конкретному случаю использования.

1 Ответ

0 голосов
/ 09 ноября 2018

Предисловие: Использование time.Timer является рекомендуемым способом, использование time.After() здесь только для демонстрации и обоснования. Пожалуйста, используйте второй подход.


Использование time.After() (не рекомендуется для этого)

Если вы добавите time.After() в ветвь case, это будет «сбрасываться» в каждой итерации, потому что каждый раз будет возвращаться новый канал, так что это работает:

func doingSomething(listenCh <-chan string) {
    for {
        select {
        case mystr := <-listenCh:
            log.Println("Received", mystr)
        case <-time.After(1 * time.Second):
            log.Println("Timeout")
            return
        }
    }
}

(я использовал 1-секундный таймаут для тестирования на игровой площадке Go).

Мы можем проверить это как:

ch := make(chan string)
go func() {
    for i := 0; i < 3; i++ {
        ch <- fmt.Sprint(i)
        time.Sleep(500 * time.Millisecond)
    }
}()
doingSomething(ch)

Вывод (попробуйте на Go Playground ):

2009/11/10 23:00:00 Received 0
2009/11/10 23:00:00 Received 1
2009/11/10 23:00:01 Received 2
2009/11/10 23:00:02 Timeout

Использование time.Timer (рекомендуемое решение)

Если есть высокая скорость приема от канала, это может быть немного бесполезной тратой ресурсов, так как новый таймер создается и используется под капотом time.After(), который волшебным образом не останавливается и не собирает мусор немедленно когда он больше не нужен, если вы получаете значение из канала до истечения времени ожидания.

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

Вот как это будет выглядеть:

func doingSomething(listenCh <-chan string) {
    d := 1 * time.Second
    t := time.NewTimer(d)
    for {
        select {
        case mystr := <-listenCh:
            log.Println("Received", mystr)
            if !t.Stop() {
                <-t.C
            }
            t.Reset(d)
        case <-t.C:
            log.Println("Timeout")
            return
        }
    }
}

Тестирование и вывод одинаковы. Попробуйте это на Go Playground .

...