Go
В стандартной библиотеке языка предусмотрено два типа таймеров.Timer
а такжеTicker
.Timer
указанныйduration
Срабатывает по прошествии времени, в собственное времяchannel
Отправить текущее время, послеTimer
Остановить время.Ticker
любой другойduration
time отправит текущую точку времени в свое собственное времяchannel
, используя время таймераchannel
Многие функции, связанные с таймингом, могут быть реализованы.
Статья в основном охватывает следующее содержание:
-
Timer
а такжеTicker
Представление внутренней структуры таймера -
Timer
а такжеTicker
Как использовать и меры предосторожности - как сделать это правильно
Reset
таймер
Внутреннее представление таймера
Оба таймера основаны наGo
Таймеры времени выполнения для языковruntime.timer
осуществленный,rumtime.timer
Структура представлена следующим образом:
type timer struct {
pp puintptr
when int64
period int64
f func(interface{}, uintptr)
arg interface{}
seq uintptr
nextwhen int64
status uint32
}
rumtime.timer
Значение полей в структуре
-
when
— время пробуждения текущего таймера; -
period
— интервал между двумя пробуждениями; -
f
— функция, которая будет вызываться при каждом пробуждении таймера; -
arg
— вызывается при пробуждении таймераf
входящие параметры; -
nextWhen
- таймер включенtimerModifiedLater/timerModifiedEairlier
статус, используемый для установкиwhen
поле; -
status
— состояние таймера;
здесьruntime.timer
Только собственное представление времени выполнения таймера, внешний таймерtime.Timer
а такжеtime.Ticker
Структура представлена следующим образом:
type Timer struct {
C <-chan Time
r runtimeTimer
}
type Ticker struct {
C <-chan Time
r runtimeTimer
}
Timer.C
а такжеTicker.C
это время в таймереchannel
, давайте посмотрим, как использовать эти два таймера и на что следует обратить внимание при их использовании.
Таймер таймер
time.Timer
Таймер должен пройтиtime.NewTimer
,time.AfterFunc
илиtime.After
создание функции. Когда таймер истекает, истекшее время отправляется на таймер, удерживаемый таймером.channel
,подпискаchannel
изgoroutine
получит время, когда таймер истечет.
по таймеруTimer
Пользователи могут определить свою собственную логику тайм-аута, особенно при работе сselect
обрабатывать несколькоchannel
овертайм, синглchannel
Это особенно удобно в таких ситуациях, как тайм-аут чтения и записи.Timer
Обычное использование выглядит следующим образом:
//使用time.AfterFunc:
t := time.AfterFunc(d, f)
//使用time.After:
select {
case m := <-c:
handle(m)
case <-time.After(5 * time.Minute):
fmt.Println("timed out")
}
// 使用time.NewTimer:
t := time.NewTimer(5 * time.Minute)
select {
case m := <-c:
handle(m)
case <-t.C:
fmt.Println("timed out")
}
time.AfterFunc
создан таким образомTimer
, по истечении тайм-аута он будет выполнен в отдельномgoroutine
выполнить функциюf
.
func AfterFunc(d Duration, f func()) *Timer {
t := &Timer{
r: runtimeTimer{
when: when(d),
f: goFunc,
arg: f,
},
}
startTimer(&t.r)
return t
}
func goFunc(arg interface{}, seq uintptr) {
go arg.(func())()
}
сверхуAfterFunc
Исходный код виден снаружиf
Параметр напрямую не назначается таймеру времени выполнения.f
, но как функция-оболочкаgoFunc
переданные параметры.goFunc
начнется новыйgoroutine
для выполнения внешних переданных функцийf
. Это связано с тем, что все функции событий таймера создаютсяGo
уникальный во время выполненияgoroutine
timerproc
В ходе выполнения. чтобы не блокироватьtimerproc
выполнение, должен начать новыйgoroutine
Выполнить функцию события с истекшим сроком действия.
заNewTimer
а такжеAfter
Два метода созданияTimer
По истечении таймаута выполнить функцию, встроенную в стандартную библиотеку:sendTime
.
func NewTimer(d Duration) *Timer {
c := make(chan Time, 1)
t := &Timer{
C: c,
r: runtimeTimer{
when: when(d),
f: sendTime,
arg: c,
},
}
startTimer(&t.r)
return t
}
func sendTime(c interface{}, seq uintptr) {
select {
case c.(chan Time) <- Now():
default:
}
}
sendTime
Отправить текущее время наTimer
времяchannel
середина. то это действие не заблокируетсяtimerproc
выполнение? Ответ нет, причина в томNewTimer
создает буферизованныйchannel
Так что неважноTimer.C
этоchannel
Есть ли получательsendTime
может отправлять текущее время без блокировки наTimer.C
,а такжеsendTime
Также добавлена двойная страховка: черезselect
судитьTimer.C
изBuffer
Независимо от того, заполнен ли он, как только он будет заполнен, он выйдет напрямую без блокировки.
Timer
изStop
метод предотвращения срабатывания таймера, вызовитеStop
Метод успешно останавливает срабатывание таймера и возвращаетсяtrue
, если таймер истек или былStop
остановлено, звоните сноваStop
метод вернетfalse
.
Go
Среда выполнения поддерживает все таймеры в минимальной кучеMin Heap
середина,Stop
Таймер просто удаляет этот таймер из кучи.
Тикерный таймер
Ticker
Временные события могут запускаться периодически, каждый раз, когда достигается указанный временной интервал, событие будет запускаться.
time.Ticker
нужно пройтиtime.NewTicker
илиtime.Tick
Создайте.
// 使用time.Tick:
go func() {
for t := range time.Tick(time.Minute) {
fmt.Println("Tick at", t)
}
}()
// 使用time.Ticker
var ticker *time.Ticker = time.NewTicker(1 * time.Second)
go func() {
for t := range ticker.C {
fmt.Println("Tick at", t)
}
}()
time.Sleep(time.Second * 5)
ticker.Stop()
fmt.Println("Ticker stopped")
ноtime.Tick
Редко используется, если только вы не хотите использовать его на протяжении всего времени жизни программы.time.Ticker
времяchannel
. в официальном документеtime.Tick
Описание:
time.Tick
базаTicker
не может быть восстановлен сборщиком мусора;
так что используйтеtime.Tick
Будьте осторожны при использовании, чтобы избежать случайного использованияtime.NewTicker
вернутьTicker
альтернатива.
NewTicker
Создайте таймер сNewTimer
Время, удерживаемое созданным таймеромchannel
с тайникомchannel
, функция, выполняемая после каждого триггера, такжеsendTime
, что гарантирует, что независимо от ошибочного приемникаTicker
Ни один из них не будет блокироваться при запуске события времени:
func NewTicker(d Duration) *Ticker {
if d <= 0 {
panic(errors.New("non-positive interval for NewTicker"))
}
// Give the channel a 1-element time buffer.
// If the client falls behind while reading, we drop ticks
// on the floor until the client catches up.
c := make(chan Time, 1)
t := &Ticker{
C: c,
r: runtimeTimer{
when: when(d),
period: int64(d),
f: sendTime,
arg: c,
},
}
startTimer(&t.r)
return t
}
Проблемы, о которых следует помнить при сбросе таймера
оReset
Предложение по использованию, описание в документе:
Необходимо соблюдать осторожность при сбросе таймера, чтобы не конкурировать с истечением текущего таймера, отправляя время в t.C. Если программа уже получила значение от t.C, известно, что таймер истек, и можно напрямую использовать t.Reset. Если программа не получила значения от t.C, то таймер должен быть сначала остановлен, и - если при использовании t.Stop сообщается, что таймер истек, то слить значение его канала.
Например:
if !t.Stop() { <-t.C } t.Reset(d)
В примере нижеproducer goroutine
отправлять один на канал каждую секундуfalse
значение, подождите одну секунду после завершения цикла, а затем отправьте его в каналtrue
ценность. существуетconsumer goroutine
Здесь он пытается прочитать значение из канала через цикл и устанавливает максимальное время ожидания на 5 секунд с помощью таймера.Если время ожидания таймера истекло, выведите текущее время и попробуйте следующий цикл, если чтение из канала не является ожидаемым значением (ожидаемое значение равноtrue
), попробуйте снова прочитать с канала и сбросьте таймер.
func main() {
c := make(chan bool)
go func() {
for i := 0; i < 5; i++ {
time.Sleep(time.Second * 1)
c <- false
}
time.Sleep(time.Second * 1)
c <- true
}()
go func() {
// try to read from channel, block at most 5s.
// if timeout, print time event and go on loop.
// if read a message which is not the type we want(we want true, not false),
// retry to read.
timer := time.NewTimer(time.Second * 5)
for {
// timer is active , not fired, stop always returns true, no problems occurs.
if !timer.Stop() {
<-timer.C
}
timer.Reset(time.Second * 5)
select {
case b := <-c:
if b == false {
fmt.Println(time.Now(), ":recv false. continue")
continue
}
//we want true, not false
fmt.Println(time.Now(), ":recv true. return")
return
case <-timer.C:
fmt.Println(time.Now(), ":timer expired")
continue
}
}
}()
//to avoid that all goroutine blocks.
var s string
fmt.Scanln(&s)
}
Вывод программы следующий:
2020-05-13 12:49:48.90292 +0800 CST m=+1.004554120 :recv false. continue
2020-05-13 12:49:49.906087 +0800 CST m=+2.007748042 :recv false. continue
2020-05-13 12:49:50.910208 +0800 CST m=+3.011892138 :recv false. continue
2020-05-13 12:49:51.914291 +0800 CST m=+4.015997373 :recv false. continue
2020-05-13 12:49:52.916762 +0800 CST m=+5.018489240 :recv false. continue
2020-05-13 12:49:53.920384 +0800 CST m=+6.022129708 :recv true. return
Пока проблем нет.Использование Reset для сброса таймера тоже работает.Далее надоproducer goroutin
внести некоторые изменения, мы ставимproducer goroutine
Измените логику отправки значений каждую секунду на каждый6
секунд для отправки значения, в то время какconsumer gouroutine
пробег и таймер еще5
Истекает в секундах.
// producer
go func() {
for i := 0; i < 5; i++ {
time.Sleep(time.Second * 6)
c <- false
}
time.Sleep(time.Second * 6)
c <- true
}()
Запустите его снова, и вы обнаружите, что программа произошлаdeadlock
Заблокировано сразу после истечения таймера первого отчета:
2020-05-13 13:09:11.166976 +0800 CST m=+5.005266022 :timer expired
Где заблокирована программа? Да, это истощаетtimer.C
При перекрытии канала (по-английски дренажный канал уподобляется сливу воды в трубопроводе, в программеtimer.C
В конвейере больше нет неполученных значений).
if !timer.Stop() {
<-timer.C
}
timer.Reset(time.Second * 5)
producer goroutine
поведение отправки изменилось,comsumer goroutine
Есть событие, что таймер истекает до получения первых данных,for
Перейдите к следующему циклу. В настоящее времяtimer.Stop
функция больше не возвращаетtrue
, ноfalse
, поскольку срок действия таймера истек, упомянутая выше минимальная куча, в которой хранятся все активные таймеры, больше не содержит таймер. В настоящее времяtimer.C
Нет данных вdrain channel
Код будетconsumer goroutine
заблокирован.
В этом случае следует непосредственноReset
таймер вместо явногоdrain channel
. Как совместить эти две ситуации в одну? мы можем использоватьselect
обернутьdrain channel
операции, так что независимо отchannel
есть ли данные вdrain
не заблокирует.
//consumer
go func() {
// try to read from channel, block at most 5s.
// if timeout, print time event and go on loop.
// if read a message which is not the type we want(we want true, not false),
// retry to read.
timer := time.NewTimer(time.Second * 5)
for {
// timer may be not active, and fired
if !timer.Stop() {
select {
case <-timer.C: //try to drain from the channel
default:
}
}
timer.Reset(time.Second * 5)
select {
case b := <-c:
if b == false {
fmt.Println(time.Now(), ":recv false. continue")
continue
}
//we want true, not false
fmt.Println(time.Now(), ":recv true. return")
return
case <-timer.C:
fmt.Println(time.Now(), ":timer expired")
continue
}
}
}()
Запустите модифицированную программу и обнаружите, что программа не будет заблокирована, а чтение каналов может выполняться в обычном режиме.true
После значения он выйдет сам по себе. Результат выглядит следующим образом:
2020-05-13 13:25:08.412679 +0800 CST m=+5.005475546 :timer expired
2020-05-13 13:25:09.409249 +0800 CST m=+6.002037341 :recv false. continue
2020-05-13 13:25:14.412282 +0800 CST m=+11.005029547 :timer expired
2020-05-13 13:25:15.414482 +0800 CST m=+12.007221569 :recv false. continue
2020-05-13 13:25:20.416826 +0800 CST m=+17.009524859 :timer expired
2020-05-13 13:25:21.418555 +0800 CST m=+18.011245687 :recv false. continue
2020-05-13 13:25:26.42388 +0800 CST m=+23.016530193 :timer expired
2020-05-13 13:25:27.42294 +0800 CST m=+24.015582511 :recv false. continue
2020-05-13 13:25:32.425666 +0800 CST m=+29.018267054 :timer expired
2020-05-13 13:25:33.428189 +0800 CST m=+30.020782483 :recv false. continue
2020-05-13 13:25:38.432428 +0800 CST m=+35.024980796 :timer expired
2020-05-13 13:25:39.428343 +0800 CST m=+36.020887629 :recv true. return
Суммировать
Выше описано более подробноGo
Языковые таймеры, их использование и меры предосторожности кратко изложены в следующих ключевых моментах:
-
Timer
а такжеTicker
таймер времени выполненияruntime.timer
реализовано на основе . - Функции событий всех таймеров в среде выполнения контролируются уникальной средой выполнения.
goroutine
timerproc
вызывать. -
time.Tick
созданныйTicker
не будетgc
Переработка, если вы не можете его использовать. -
Timer
а такжеTicker
времяchannel
Оба канала с буфером. -
time.After
,time.NewTimer
,time.NewTicker
Созданный таймер будет выполнен, когда он сработаетsendTime
. -
sendTime
А буферизованный временной канал таймера гарантирует, что таймер не блокирует программу. -
Reset
Следите за таймерамиdrain channel
Есть состояние гонки с истечением таймера.