Understanding Golang sleep() function

When we write a simple Golang program test, like

1
2
3
4
5
6
7
package main

import "time"

func main() {
time.Sleep(1000 * time.Millisecond)
}

How Golang to implement these functions?

In this blog, I simply analyze the timer that is used for Sleep() function of Golang v1.12.

TLDR

Golang Sleep() function utilizes hrtimer to set timeout of sleep.

Full article

In Linux POSIX (Portable Operating System Interface), sleep function has been implemented. You can try

1
$ sleep 1

in shell for sleeping one second, which is implemented by linux timer nanosleep.

However, Golang does not use timer for its Sleep() directly. If you check code in go/src/time/sleep.go, you’ll find that timer is not implemented in this file. Instead, it is implemented in go/src/runtime/time.go. In the function timeSleep(ns int64),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func timeSleep(ns int64) {
if ns <= 0 {
return
}

gp := getg()
t := gp.timer
if t == nil {
t = new(timer)
gp.timer = t
}
*t = timer{}
t.when = nanotime() + ns
t.f = goroutineReady
t.arg = gp
tb := t.assignBucket()
lock(&tb.lock)
if !tb.addtimerLocked(t) {
unlock(&tb.lock)
badTimer()
}
goparkunlock(&tb.lock, waitReasonSleep, traceEvGoSleep, 2)
}

before line 17, function is creating a timer. In line 17, it locks timer. So, Sleep() sets up timer by locking a resource ns nanoseconds. If we use strace to run a go application,

1
2
3
4
$ strace ./test
...
futex(0x55ca00, FUTEX_WAIT_PRIVATE, 0, NULL) = -1
...

Sleep() call futex function to sleep.

In the go/src/runtime/lock_futex.go, lock function

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
func lock(l *mutex) {
gp := getg()

if gp.m.locks < 0 {
throw("runtime·lock: lock count")
}
gp.m.locks++

// Speculative grab for lock.
v := atomic.Xchg(key32(&l.key), mutex_locked)
if v == mutex_unlocked {
return
}

// wait is either MUTEX_LOCKED or MUTEX_SLEEPING
// depending on whether there is a thread sleeping
// on this mutex. If we ever change l->key from
// MUTEX_SLEEPING to some other value, we must be
// careful to change it back to MUTEX_SLEEPING before
// returning, to ensure that the sleeping thread gets
// its wakeup call.
wait := v

// On uniprocessors, no point spinning.
// On multiprocessors, spin for ACTIVE_SPIN attempts.
spin := 0
if ncpu > 1 {
spin = active_spin
}
for {
// Try for lock, spinning.
for i := 0; i < spin; i++ {
for l.key == mutex_unlocked {
if atomic.Cas(key32(&l.key), mutex_unlocked, wait) {
return
}
}
procyield(active_spin_cnt)
}

// Try for lock, rescheduling.
for i := 0; i < passive_spin; i++ {
for l.key == mutex_unlocked {
if atomic.Cas(key32(&l.key), mutex_unlocked, wait) {
return
}
}
osyield()
}

// Sleep.
v = atomic.Xchg(key32(&l.key), mutex_sleeping)
if v == mutex_unlocked {
return
}
wait = mutex_sleeping
futexsleep(key32(&l.key), mutex_sleeping, -1)
}
}

line 57 calls futexsleep.

In go/src/runtime/os_linux.go, function futexsleep is defined as to call futex function of linux.

So far, we can know that Golang utilize Linux futex function to set timer for Sleep() function.

In Linux , futex is implemented in linux/kernel/futex.c. hrtimer is used as timer of futex. hrtimer is a high-resolution timer compared with the other timers (itimers, POSIX timers,nanosleep,precise in-kernel timing).

In summary, when we call Sleep() function in Golang,

App calls sleep of Golang -> Golang calls futex of Linux -> futex utilizes hrtimer to be timer.

Recommended Posts