GoВ стандартной библиотеке языка предусмотрено два типа таймеров.Timerа такжеTicker.TimerуказанныйdurationСрабатывает по прошествии времени, в собственное времяchannelОтправить текущее время, послеTimerОстановить время.Tickerлюбой другойdurationtime отправит текущую точку времени в свое собственное время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реализовано на основе . - Функции событий всех таймеров в среде выполнения контролируются уникальной средой выполнения.
goroutinetimerprocвызывать. -
time.TickсозданныйTickerне будетgcПереработка, если вы не можете его использовать. -
Timerа такжеTickerвремяchannelОба канала с буфером. -
time.After,time.NewTimer,time.NewTickerСозданный таймер будет выполнен, когда он сработаетsendTime. -
sendTimeА буферизованный временной канал таймера гарантирует, что таймер не блокирует программу. -
ResetСледите за таймерамиdrain channelЕсть состояние гонки с истечением таймера.