01 time.timer
1
2
3
4
5
6
7
8
9
10
11
12
|
// runtime/time.go
type timer struct {
pp puintptr
when int64
period int64
f func(interface{}, uintptr)
arg interface{}
seq uintptr
nextwhen int64
status uint32
}
|
timer
is defined in runtime/time.go
.
pp
is a pointer to the current counter on p
and a pointer to the heap.
when
is the scale, indicating how often to trigger.
nextWhen
indicates the nanosecond timestamp of the next trigger, the underlying cpu time of the call.
f
is the method that needs to be executed after the trigger. arg
is the argument to the method.
A timer
has many states, and the state transformation of a timer
can be seen in the source code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
// addtimer:
// timerNoStatus -> timerWaiting
// anything else -> panic: invalid value
// deltimer:
// timerWaiting -> timerModifying -> timerDeleted
// timerModifiedEarlier -> timerModifying -> timerDeleted
// timerModifiedLater -> timerModifying -> timerDeleted
// timerNoStatus -> do nothing
// timerDeleted -> do nothing
// timerRemoving -> do nothing
// timerRemoved -> do nothing
// timerRunning -> wait until status changes
// timerMoving -> wait until status changes
// timerModifying -> wait until status changes
// modtimer:
// timerWaiting -> timerModifying -> timerModifiedXX
// timerModifiedXX -> timerModifying -> timerModifiedYY
// timerNoStatus -> timerModifying -> timerWaiting
// timerRemoved -> timerModifying -> timerWaiting
// timerDeleted -> timerModifying -> timerModifiedXX
// timerRunning -> wait until status changes
// timerMoving -> wait until status changes
// timerRemoving -> wait until status changes
// timerModifying -> wait until status changes
// cleantimers (looks in P's timer heap):
// timerDeleted -> timerRemoving -> timerRemoved
// timerModifiedXX -> timerMoving -> timerWaiting
// adjusttimers (looks in P's timer heap):
// timerDeleted -> timerRemoving -> timerRemoved
// timerModifiedXX -> timerMoving -> timerWaiting
// runtimer (looks in P's timer heap):
// timerNoStatus -> panic: uninitialized timer
// timerWaiting -> timerWaiting or
// timerWaiting -> timerRunning -> timerNoStatus or
// timerWaiting -> timerRunning -> timerWaiting
// timerModifying -> wait until status changes
// timerModifiedXX -> timerMoving -> timerWaiting
// timerDeleted -> timerRemoving -> timerRemoved
// timerRunning -> panic: concurrent runtimer calls
// timerRemoved -> panic: inconsistent timer heap
// timerRemoving -> panic: inconsistent timer heap
// timerMoving -> panic: inconsistent timer heap
|
Going further down here involves the implementation of the system CPU timer.
1
2
3
4
|
func startTimer(*runtimeTimer)
func stopTimer(*runtimeTimer) bool
func resetTimer(*runtimeTimer, int64) bool
func modTimer(t *runtimeTimer, when, period int64, f func(interface{}, uintptr), arg interface{}, seq uintptr)
|
01 time.Sleep()
time.Sleep()
also uses timer
internally to implement timer blocking, it will try to get timer
from the current g
first, if not then create a new timer
, you can think of it as an implementation of the timer
singleton pattern. Because there can be at most one time.Sleep()
running at a given moment in a g
. After setting timer
call gopark()
, pass in the timer
object to execute the timer and block the current goroutine.
- Link to the
time.Sleep()
method via the golang-specific annotation go:linkname timeSleep time.Sleep
. This linking process will be done during golang’s compilation SSA.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//go:linkname timeSleep time.Sleep
func timeSleep(ns int64) {
if ns <= 0 {
return
}
gp := getg()
t := gp.timer
if t == nil {
t = new(timer)
gp.timer = t
}
t.f = goroutineReady
t.arg = gp
t.nextwhen = nanotime() + ns
if t.nextwhen < 0 { // check for overflow.
t.nextwhen = maxWhen
}
gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceEvGoSleep, 1)
}
|
02 time.Ticker
A timer
already satisfies the loop count, but it is not exported, so it cannot be used externally; instead, a time.Ticker
is used. It wraps timer
and provides some additional operations.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// time/tick.go
type Ticker struct {
C <-chan Time // The channel on which the ticks are delivered.
r runtimeTimer
}
func NewTicker(d Duration) *Ticker {
if d <= 0 {
panic(errors.New("non-positive interval for NewTicker"))
}
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
}
|
A ticker
is also implemented at the bottom through a timer
, except that it registers the sendTime
method with the timer
, which continues to reset
the timer
and sends a message to a pipe c
after it is triggered, and is commonly used in the following way.
1
2
3
4
|
ticker := time.NewTicker(time.Second*1)
for range ticker.C {
// do something
}
|
Next, let’s look at the logic of the sendTime()
method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// It is a non-blocking method
func sendTime(c interface{}, seq uintptr) {
select {
case c.(chan Time) <- Now():
default:
}
}
// Returns the current system time
func Now() Time {
sec, nsec, mono := now()
mono -= startNano
sec += unixToInternal - minWall
if uint64(sec)>>33 != 0 {
return Time{uint64(nsec), sec + minWall, Local}
}
return Time{hasMonotonic | uint64(sec)<<nsecShift | uint64(nsec), mono, Local}
}
|
Therefore, the main logic is to use time.timer
to implement the underlying cycle timing, and then trigger sendTime()
when the time of each cycle is up, and then notify the user via the channel in this method.
03 time.Timer
In addition to the commonly used time.Ticker
, golang also provides time.Timer
, which also maintains a timer
and a channel
, and its definition is the same as `time.
1
2
3
4
5
6
|
// time/sleep.go
type Timer struct {
C <-chan Time
r runtimeTimer
}
|
When time.NewTimer()
is called.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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
}
|
Compared to time.NewTicker
, it does not assign a value to the period
field when initializing runtimeTimer
. Therefore, it is one-time, while ticker
is periodic.
04 The principle of the underlying timer implementation: the Time Heap
From golang’s source code we can trace the operation of its underlying timer
.
1
2
3
4
|
func startTimer(*runtimeTimer)
func stopTimer(*runtimeTimer) bool
func resetTimer(*runtimeTimer, int64) bool
func modTimer(t *runtimeTimer, when, period int64, f func(interface{}, uintptr), arg interface{}, seq uintptr)
|
There is only a definition here, not an implementation because it calls the C compiler at compile time and uses its associated Linux kernel implementation. Each p
has a timer
array bound to it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
type p struct {
...
// Actions to take at some time. This is used to implement the
// standard library's time package.
// Must hold timersLock to access.
timers []*timer
// Number of timers in P's heap.
// Modified using atomic instructions.
numTimers uint32
// Number of timerDeleted timers in P's heap.
// Modified using atomic instructions.
deletedTimers uint32
// Race context used while executing timer functions.
timerRaceCtx uintptr
...
}
|
It is essentially a quadtree minimum heap, and each time runtime
determines the top element of the heap by the cpu clock to see if the timestamp is less than or equal to the cpu time, and if so, the callback method corresponding to the timer
registration is executed. Therefore, each heap adjustment requires nlog(n)
time complexity. The next step is the operation of runtime
on the timer.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
func addtimer(t *timer) {
if t.when <= 0 {
throw("timer when must be positive")
}
if t.period < 0 {
throw("timer period must be non-negative")
}
if t.status != timerNoStatus {
throw("addtimer called with initialized timer")
}
t.status = timerWaiting
when := t.when
mp := acquirem()
pp := getg().m.p.ptr()
lock(&pp.timersLock)
cleantimers(pp)
doaddtimer(pp, t)
unlock(&pp.timersLock)
wakeNetPoller(when)
releasem(mp)
}
|
It will add him to the timer[]
heap of p
. Of course, this trigger will also involve the scheduling of g