When writing Go program, there is frequent need on using time.Sleep()
function to pause the logic for some time. And if jumping to the definition of this function, can see below definition:
// Sleep pauses the current goroutine for at least the duration d.
// A negative or zero duration causes Sleep to return immediately.
func Sleep(d Duration)
I's strange that there is no function body defined here. What happened? The actual definition of the function body is residing at runtime/time.go
indeed.
// timeSleep puts the current goroutine to sleep for at least ns nanoseconds.
//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 = 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)
}
How is this linked to the original time.Sleep()
function? The magic is at the line:
//go:linkname timeSleep time.Sleep
According to the official documentation:
//go:linkname localname importpath.name The //go:linkname directive instructs the compiler to use “importpath.name” as the object file symbol name for the variable or function declared as “localname” in the source code. Because this directive can subvert the type system and package modularity, it is only enabled in files that have imported "unsafe".
In above example, localname refers to timeSleep
while importpath.name refers to time.Sleep
. The reason for doing this is that time.Sleep is in time
package and it's exported while timeSleep is in runtime
package and it's un-exported. To be able to use it, this go:linkname is used. This is a bit hacky though.
Here is another example to demonstrate the usage. Assume there is a directory structure
➜ demo git:(master) ✗ tree
.
├── linkname
│ └── a.go
├── main.go
└── outer
└── world.go
The contents in a.go
package linkname
import _ "unsafe"
//go:linkname hello examples/demo/outer.World
func hello() {
println("hello,world!")
}
The contents in world.go
package outer
import (
_ "examples/demo/linkname"
)
func World()
The contents in main.go
package main
import (
"examples/demo/outer"
)
func main() {
outer.World()
}
The output is
# examples/demo/outer
outer/world.go:7:6: missing function body
The reason for the error is that go build will run with -complete flag which detects the function has no body and will report error as it assume package has no non-Go components. To workaround this, just add a file with extension name .s.
➜ demo git:(master) ✗ tree
.
├── linkname
│ └── a.go
├── main.go
└── outer
├── i.s
└── world.go
Run the program again and it will output
hello,world!
This trick is useful in some cases where there is really a need to access some un-exported functions in some packages. Use it cautiously.
Reference: https://studygolang.com/articles/15842