初入门径
查看一些官方库的源码时,经常发现找不到其具体的实现,以常用的time.Sleep()为例:
1 | // Sleep至少在持续时间d内暂停当前goroutine, |
但该方法的实现在哪里? 寻遍time包,也不见Sleep的实现.
其实其实现在 runtime/time.go:
1 | // timeSleep puts the current goroutine to sleep for at least ns nanoseconds. |
go:linkname是源码中最常用的Go指令,其官方定义如下:
1 | //go:linkname localname [importpath.name] |
这个指令告诉编译器 为当前源文件中的私有函数或者变量在编译时链接到指定的方法或变量。
因为这个指令破坏了类型系统和包的模块化,因此在使用时必须导入unsafe包.
可以看到runtime/time.go文件是有导入unsafe包的
对于//go:linkname localname [importpath.name], 这里localname对应timeSleep, importpath.name 对应time.Sleep
但为什么要这么做?
这是因为timeSleep在runtime包里,是不可导出的. 使用go:linkname的目的,就是让time包,可以调用runtime包中原本不可导出的方法
Demo实验
新建demo文件夹,结构如下:
1 | ➜ tree |
a.go:
1 | package linkname |
world.go:
1 | package outer |
main.go:
1 | package main |
执行 go run main.go,会出现报错:
1 | main.go:3:8: package note/demo/outer is not in GOROOT (/usr/local/opt/go@1.14/libexec/src/note/demo/outer) |
参考报错package xxx is not in GOROOT,
将demo文件夹放到GOPATH下:
1 | ➜ src echo $GOPATH |
执行 go run main.go:
报错如下:
1 | # demo/outer |
这是因为go build默认加会加上-complete参数,这个参数检查到**World()**没有方法,从而抛出如上错误
可以在outer文件夹中增加一个空的.s文件即可绕过这个限制(也可以选择用单独的compile命令进行编译):
1 | ➜ demo tree |
执行 go run main.go:
hello,world!
总结&注意事项
linkname注解非常广泛地应用于Go的源码中, 其指令的格式如下:
//go:linkname hello(具体的实现) demo/outer.World(导出用到哪个地方)
//后面不能有空格
//go:linkname xxx xxxxx 必须在具体实现的正上方,之间不能有空行
go:linkname引导编译器将当前(私有)方法或者变量在编译时链接到指定的位置的方法或者变量,第一个参数表示当前方法或变量,第二个参数表示目标方法或变量,因为这关指令会破坏系统和包的模块化,因此在使用时必须导入unsafe
该指令不经常用(最好在业务开发中也不要用),但了解这个指令可以帮助理解核心包的很多代码. 在标准库中,为了可以使用另一个包的unexported的方法或者变量,正常情况这些unexported资源是不可包外访问的,但是运行时用这个命令hack一下,就变得可以访问
参考:
注释竟然还有特殊用途?一文解惑 //go:linkname 指令
更多Go指令,可参考
原文链接: https://dashen.tech/2021/05/23/go-linkname/
版权声明: 转载请注明出处.