динамически изменять интервал тикера

Я хотел бы динамически изменять интервал тикера.

Я записал пример, чтобы показать вам, как я это сделал. Мой вариант использования отличается от «акселерометра», но я надеюсь, что он даст вам представление.

http://play.golang.org/p/6ANFnoE6pA

package main

import (
    "time"
    "log"
    "fmt"
)

func main() {
    interval := float64(1000)

    ticker := time.NewTicker(time.Duration(interval) * time.Millisecond)
    go func(){
        counter := 1.0
        for range ticker.C {
            log.Println("ticker accelerating to " + fmt.Sprint(interval/counter) + " ms")
            ticker = time.NewTicker(time.Duration(interval/counter) * time.Millisecond)
            counter++
        }
        log.Println("stopped")
    }()
    time.Sleep(5 * time.Second)
    log.Println("stopping ticker")
    ticker.Stop()
}

Что не так, так это то, что тикер всегда будет "тикать" каждую секунду и не ускоряться... Есть идеи?


person damoiser    schedule 18.04.2016    source источник
comment
В коде есть гонка данных   -  person Bleeding Fingers    schedule 18.04.2016
comment
Поскольку цикл for по-прежнему использует канал из старого объекта тикера, а не использует каналы из новых объектов тикера.   -  person Nipun Talukdar    schedule 18.04.2016
comment
Спасибо за ваши отзывы. @BleedingFingers Я вижу гонку данных (в тикерной переменной), но в этом случае не следует паниковать? В противном случае указатель следует заменить новым. @ NipunTalukdar Я тоже так думал, если это правильно, то это означает, что указатель тикера кэшируется в цикле диапазона, и переопределение невозможно. Я попробую это на другом примере.   -  person damoiser    schedule 18.04.2016
comment
Да, @NipunTalukdar, я предполагаю, что метод range кэширует переменную для зацикливания, тогда я думаю, что переопределение тикера, как я предложил (с использованием range), невозможно - play.golang.org/p/yZvrgURz4o   -  person damoiser    schedule 18.04.2016


Ответы (4)


После ответа @fzerorubigd, но немного более полного.

Как было сказано ранее, мы не можем использовать range для этого случая, потому что цикл range кэширует переменную, которую нужно обрезать, и затем ее нельзя перезаписать (пример здесь: http://play.golang.org/p/yZvrgURz4o )

Затем мы должны использовать комбинированный цикл for-select. Далее рабочее решение:

http://play.golang.org/p/3uJrAIhnTQ

package main

import (
    "time"
    "log"
    "fmt"
)

func main() {
    start_interval := float64(1000)
    quit := make(chan bool)

    go func(){
        ticker := time.NewTicker(time.Duration(start_interval) * time.Millisecond)
        counter := 1.0

        for {
            select {
            case <-ticker.C:
                log.Println("ticker accelerating to " + fmt.Sprint(start_interval/counter) + " ms")
                ticker.Stop()
                ticker = time.NewTicker(time.Duration(start_interval/counter) * time.Millisecond)
                counter++
            case <-quit:
                ticker.Stop()
                log.Println("..ticker stopped!")
                return
            }
        }
    }()

    time.Sleep(5 * time.Second)

    log.Println("stopping ticker...")
    quit<-true

    time.Sleep(500 * time.Millisecond) // just to see quit messages
}
person damoiser    schedule 19.04.2016
comment
Увеличит ли создание нового time.NewTicker накладные расходы на сборку мусора? - person chinuy; 20.02.2018
comment
хороший вопрос @chinuy - я бы сказал, что там нужен GC, даже если я делаю замену var ticker. Если вы посмотрите на код time.NewTicker ЗДЕСЬ, вы можно увидеть, что он создает некоторые локальные структуры, которые в какой-то момент должны быть собраны сборщиком мусора. В заключение: да, это увеличивает накладные расходы GC - person damoiser; 21.02.2018

Как упомянул Нипун Талукдар, «для» захватывает канал и использует ту же ссылку для итерации. это исправлено, если вы используете его так:

детская площадка

package main

import (
    "fmt"
    "log"
    "time"
)

func main() {
    interval := float64(1000)

    ticker := time.NewTicker(time.Duration(interval) * time.Millisecond)
    go func() {
        counter := 1.0
        for {
            select {
            case <-ticker.C:
                log.Println("ticker accelerating to " + fmt.Sprint(interval/counter) + " ms")
                ticker = time.NewTicker(time.Duration(interval/counter) * time.Millisecond)
                counter++
            }
        }
        log.Println("stopped")
    }()
    time.Sleep(5 * time.Second)
    log.Println("stopping ticker")
    ticker.Stop()
}
person fzerorubigd    schedule 18.04.2016
comment
Спасибо за Ваш ответ. Да, я тоже подумал, что использование for-loop вместо range-loop может решить проблему. Только обратите внимание, что при использовании for-цикла вы должны управлять входящим каналом с select-ловушкой. Чтобы не сбивать с толку других разработчиков - я приму ваш ответ, если вы отредактируете for-код, используя правильно select-кейсы. - person damoiser; 18.04.2016
comment
Выбор не делает ничего особенного, когда случай единичный. но я понимаю, и код обновлен. - person fzerorubigd; 18.04.2016
comment
мой отзыв был связан с функцией выхода - чтобы освободить все переменные, используемые в горутинах, должен вернуться цикл for. Это происходит даже в единичном случае. Кстати, об этом не спрашивали, просто хочу уточнить ;-) - person damoiser; 18.04.2016
comment
Стоит заметить, что когда вы вызываете ticker.Stop(), вы на самом деле не закрываете его канал, поэтому линия log.Println("stopped") никогда не будет достигнута. Вы должны сохранить вторичный канал, чтобы сигнализировать, когда вы закончите, а затем select между тикером и вторичным каналом. Вы также должны останавливать каждый старый тикер перед созданием нового. - person hbejgel; 18.04.2016
comment
@hbejgel тикер.C - это канал только для приема. вы не можете закрыть канал только для приема. владелец канала, у которого есть вся ссылка на него, может закрыть его. см. golang.org/pkg/time/#Ticker - person fzerorubigd; 19.04.2016
comment
Я знаю об этом, и из-за этого вы никогда не выйдете из цикла for внутри горутины. Поэтому горутина никогда не напечатает ожидаемую строку stopped - person hbejgel; 19.04.2016

Как насчет этого кода:

https://play.golang.org/p/wyOTVxUW5Xj

package main

import (
    "fmt"
    "log"
    "time"
)

func main() {
    startInterval := float64(1000)
    quit := make(chan bool)

    go func() {
        counter := 1.0
        for {
            select {
            case <-time.After(time.Duration(startInterval/counter) * time.Millisecond):
                log.Println("ticker accelerating to " + fmt.Sprint(startInterval/counter) + " ms")
                counter++
            case <-quit:
                log.Println("..ticker stopped!")
                return
            }
        }
    }()

    time.Sleep(5 * time.Second)
    log.Println("stopping ticker...")
    quit <- true
    time.Sleep(500 * time.Millisecond) // just to see quit messages
}
person sgon00    schedule 13.08.2019

вот почему в go1.15 создается ticker.Reset, вам не нужно создавать новый тикер, обновите существующую продолжительность тикеров с помощью ticker.Reset("new duration"), и теперь у вас не будет проблем с кешем

Игровая площадка

package main

import (
    "fmt"
    "log"
    "time"
)

func main() {
    interval := float64(1000)

    ticker := time.NewTicker(time.Duration(interval) * time.Millisecond)
    go func(){
        counter := 1.0
        for range ticker.C {
            log.Println("ticker accelerating to " + fmt.Sprint(interval/counter) + " ms")
            ticker.Reset(time.Duration(interval/counter) * time.Millisecond)
            counter++
        }
        log.Println("stopped")
    }()
    time.Sleep(5 * time.Second)
    log.Println("stopping ticker")
    ticker.Stop()
}

Причина, по которой в вашем примере есть проблема с кешем, заключается в том, что когда вы переназначаете переменную ticker со структурой *time.ticker, вы просто отвязываете исходную структуру *time.ticker от переменной ticker, но цикл все еще привязан к исходному каналу тикера, вам нужно переназначить новый цикл для новый time.ticker.c

person Isaac Weingarten    schedule 19.10.2020
comment
Хотя сброс работает, в документации указано Reset should be invoked only on stopped or expired timers with drained channels. Так что я бы не советовал сбрасывать активный тикер. - person Chen A.; 24.10.2020
comment
вы путаете его с time.timer{}.Reset() на time.tiker{}.Reset() нет такого износа, вот документация для этого - person Isaac Weingarten; 26.10.2020
comment
Стоит отметить, что метод ticker.Reset доступен с версии go1.15. - person Chen A.; 30.10.2020
comment
Пожалуйста, объясните, в чем проблема с go1.15? Компилятор будет работать со старым кодом - person Isaac Weingarten; 02.11.2020
comment
@Исаак, это не проблема, но стоит отметить. Я использовал более раннюю версию (1.14), и она не работала. - person Chen A.; 03.11.2020