Сброс тикера - PullRequest
       57

Сброс тикера

0 голосов
/ 10 октября 2019

Рассмотрим сервер, инициализированный ресурсом таймера / тикера, который будет запускать каждые тики t (t - 20 мс в моем примере). Каждый раз, когда сервер прослушивает что-то в сети (например, периодический сигнал от пиров), он должен сбросить таймер. С другой стороны, если таймер истекает без сброса (например, все его одноранговые узлы мертвы), он вызывает какое-то событие (в моем примере я просто печатаю время от запуска программы).

У меня проблемы с реализацией этого поведения с помощью time.Ticker. Сброс таймера, кажется, работает (он не срабатывает в течение первых 50 мс), но тикер не активен (не тикает каждые 20 мс) после этого.

package main

import (
    "fmt"
    "time"
)

var wallclock time.Time

type server struct {
    timeout *time.Ticker
    stop    chan bool
}

func (srv *server) start() {
    for {
        select {
        case <-srv.timeout.C:
            {
                elapsed := time.Since(wallclock)
                fmt.Println("timed out after ", elapsed, " elapsed from start ")
            }
        case <-srv.stop:
            {
                return
            }
        }
    }
}

func main() {
    wallclock = time.Now()
    //make the server with a timer that will fire every 20ms
    srv := server{
        timeout: time.NewTicker(20 * time.Millisecond),
        //channel to indicate the server to stop listening
        stop:    make(chan bool),
    }
    //start listening on a different thread 
    go srv.start()
    for i := 0; i < 5; i++ {
        //reset it every 10ms
        time.Sleep(10 * time.Millisecond)
        srv.timeout.Stop()
        //as the reset frequency is higher, 
        //I'm not expecting this to fire within 
        //the first 50ms (5*10ms)
        srv.timeout = time.NewTicker(20 * time.Millisecond)
    }
    //sleep for 110ms
    //I'm expecting the timer to fire at least 5 times here
    time.Sleep(110 * time.Millisecond)
    //stop listening
    srv.stop <- true
    fmt.Println("Hi from tckr!")
}

Я ожидаю увидеть что-то вроде

timed out after ~70ms elapsed from start
timed out after ~90ms elapsed from start
timed out after ~110ms elapsed from start
timed out after ~130ms elapsed from start
timed out after ~150ms elapsed from start
Hi from tckr!

Пять раз, потому что я позволил основному потоку спать в течение 110 мс, а таймер 20 мс мог срабатывать пять раз за этот интервал.

Но я просто вижу Hi from tckr!. srv.timeout = time.NewTicker(20 * time.Millisecond) является правильным способом сброса Ticker?

Если я не остановлю тикер в цикле for (srv.timeout.Stop()), тикер, кажется, продолжает тикать. Вот пример вывода после комментирования srv.timeout.Stop().

timed out after  20.6872ms  elapsed from start
timed out after  41.4278ms  elapsed from start
timed out after  61.8747ms  elapsed from start
timed out after  72.7793ms  elapsed from start
timed out after  94.1448ms  elapsed from start
timed out after  112.5283ms  elapsed from start
timed out after  134.0131ms  elapsed from start
timed out after  152.5846ms  elapsed from start
Hi from tckr!

Я не хочу, чтобы тикер срабатывал в течение первых 50 мс (т. Е. Я не хочу видеть первые две строки с 20,6872 мс и 41,4278 мс).

1 Ответ

1 голос
/ 10 октября 2019

Помните, для select операторов :

Для всех случаев в операторе, операнды канала операций приема и выражения канала и правой частиоператоры отправки оцениваются ровно один раз, в исходном порядке, после ввода оператора выбора. Результатом является набор каналов для приема или отправки и соответствующие значения для отправки.

Это означает, что, как только start вводит свой оператор select, он оценивает srv.timeout.C идержится на канале;любое изменение srv.timeout после его ввода в select не окажет влияния на select, оно все еще будет ожидать получения от канала, который у него был ранее.

Вы можете обойти это, добавивдругой канал (в данном примере с именем ping), чтобы вы могли сообщить start, что канал меняется (или, возможно, переместить всю логику сброса в start):

type server struct {
    timeout *time.Ticker
    stop    chan bool
    ping    chan struct{}
}

func (srv *server) start() {
    for {
        select {
        case <-srv.timeout.C:
            elapsed := time.Since(wallclock)
            fmt.Println("timed out after ", elapsed, " elapsed from start ")
        case <-srv.ping:
            // do nothing & let the loop iterate
            // OR
            srv.timeout.Stop()
            srv.timeout = time.NewTicker(20 * time.Millisecond)
        case <-srv.stop:
            return
        }
    }
}

// in main()
go srv.start()
for i := 0; i < 5; i++ {
    //reset it every 10ms
    time.Sleep(10 * time.Millisecond)
    srv.ping <- struct{}{}

    // possibly shift the below logic to start()'s ping handler case
    srv.timeout.Stop()
    srv.timeout = time.NewTicker(20 * time.Millisecond)
}
...