В следующем сценарии сетевой объект всегда ждет TimeOutTime
секунд, прежде чем выполнить определенную задачу X
. Предположим, это время как TimerT
. В течение этого ожидания, равного TimeOutTime
секундам, если объект получает набор внешних сообщений, он должен снова сбросить те же значения TimerT
до TimeOutTime
. Если внешние сообщения не получены, ожидаемое поведение выглядит следующим образом:
- Срок действия таймера истек
- Выполните задачу X
- Сбросьте таймер еще раз до
TimeOutTime
(под reset
Я имею в виду, остановите таймер и начните все сначала)
Для имитации сценария, который я написал следующий код в Go.
package main
import (
"log"
"math/rand"
"sync"
"time"
)
const TimeOutTime = 3
const MeanArrivalTime = 4
func main() {
rand.Seed(time.Now().UTC().UnixNano())
var wg sync.WaitGroup
t := time.NewTimer(time.Second * time.Duration(TimeOutTime))
wg.Add(1)
// go routine for doing timeout event
go func() {
defer wg.Done()
for {
t1 := time.Now()
<-t.C
t2 := time.Now()
// Do.. task X .. on timeout...
log.Println("Timeout after ", t2.Sub(t1))
t.Reset(time.Second * time.Duration(TimeOutTime))
}
}()
// go routine to simulate incoming messages ...
// second go routine
go func() {
for {
// simulates a incoming message at any time
time.Sleep(time.Second * time.Duration(rand.Intn(MeanArrivalTime)))
// once any message is received reset the timer to TimeOutTime seconds again
t.Reset(time.Second * time.Duration(TimeOutTime))
}
}()
wg.Wait()
}
После запуска этой программы с использованием флага -race
она показывает DATA_RACE
:
==================
WARNING: DATA RACE
Write at 0x00c0000c2068 by goroutine 8:
time.(*Timer).Reset()
/usr/local/go/src/time/sleep.go:125 +0x98
main.main.func1()
/home/deka/Academic/go/src/main/test.go:29 +0x18f
Previous write at 0x00c0000c2068 by goroutine 9:
time.(*Timer).Reset()
/usr/local/go/src/time/sleep.go:125 +0x98
main.main.func2()
/home/deka/Academic/go/src/main/test.go:42 +0x80
Goroutine 8 (running) created at:
main.main()
/home/deka/Academic/go/src/main/test.go:20 +0x1d3
Goroutine 9 (running) created at:
main.main()
/home/deka/Academic/go/src/main/test.go:35 +0x1f5
==================
Затем я использовал Mutex, чтобы обернуть Reset()
вызов внутри Mutex.
основной пакет
import (
"log"
"math/rand"
"sync"
"time"
)
const TimeOutTime = 3
const MeanArrivalTime = 4
func main() {
rand.Seed(time.Now().UTC().UnixNano())
var wg sync.WaitGroup
t := time.NewTimer(time.Second * time.Duration(TimeOutTime))
wg.Add(1)
var mu sync.Mutex
// go routine for doing timeout event
go func() {
defer wg.Done()
for {
t1 := time.Now()
<-t.C
t2 := time.Now()
// Do.. task X .. on timeout...
log.Println("Timeout after ", t2.Sub(t1))
mu.Lock()
t.Reset(time.Second * time.Duration(TimeOutTime))
mu.Unlock()
}
}()
// go routine to simulate incoming messages ...
// second go routine
go func() {
for {
// simulates a incoming message at any time
time.Sleep(time.Second * time.Duration(rand.Intn(MeanArrivalTime)))
// once any message is received reset the timer to TimeOutTime seconds again
mu.Lock()
t.Reset(time.Second * time.Duration(TimeOutTime))
mu.Unlock()
}
}()
wg.Wait()
}
После этого код, кажется, работает нормально, исходя из следующих наблюдений.
Если я заменю строку
time.Sleep(time.Second * time.Duration(rand.Intn(MeanArrivalTime)))
во второй программе go с постоянным временем сна 4 seconds
и TimeOutTime
постоянным при 3 seconds
.
Выходной сигнал Программа:
2020/02/29 20:10:11 Timeout after 3.000160828s
2020/02/29 20:10:15 Timeout after 4.000444017s
2020/02/29 20:10:19 Timeout after 4.000454657s
2020/02/29 20:10:23 Timeout after 4.000304877s
В приведенном выше выполнении подпрограмма 2nd
go сбрасывает active timer
после того, как таймер провел первоначальную одну секунду. Из-за чего срок действия timer
истекает через 4
секунд со второго отпечатка и далее.
Теперь, когда я проверил документацию Reset()
, я обнаружил следующее:
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Reset changes the timer to expire after duration d.
// It returns true if the timer had been active, false if the timer had
// expired or been stopped.
//
// Reset should be invoked only on stopped or expired timers with drained channels.
// If a program has already received a value from t.C, the timer is known
// to have expired and the channel drained, so t.Reset can be used directly.
// If a program has not yet received a value from t.C, however,
// the timer must be stopped and—if Stop reports that the timer expired
// before being stopped—the channel explicitly drained:
//
// if !t.Stop() {
// <-t.C
// }
// t.Reset(d)
//
// This should not be done concurrent to other receives from the Timer's
// channel.
//
// Note that it is not possible to use Reset's return value correctly, as there
// is a race condition between draining the channel and the new timer expiring.
// Reset should always be invoked on stopped or expired channels, as described above.
// The return value exists to preserve compatibility with existing programs.
Я нашел эту диаграмму: (ссылка: https://blogtitle.github.io/go-advanced-concurrency-patterns-part-2-timers/)
Имея в виду биграмму, кажется, что мне нужно использовать,
if !t.Stop() {
<-t.C
}
t.Reset(d)
в 2nd
go рутина. В этом случае мне также необходимо выполнить правильную блокировку в обеих подпрограммах go, чтобы избежать бесконечного ожидания на канале.
Я не понимаю сценарий, при котором следует выполнять t.Stop() + draining of the channel (<-t.C)
. В каком случае это требуется? В моем примере я не использую значения чтения канала. Могу ли я вызвать Reset () без вызова Stop ()?