之前在看mutex
、channel
等源码的时候,发现很多都会调用runtime_SemacquireMutex
,runtime_doSpin
,runtime_xxx
等。当你跳转过去后,会发现没有实际的实现。类似c++中的.h文件。后全量搜索后,你会发现会有如下方法:
//go:linkname sync_runtime_SemacquireMutex sync.runtime_SemacquireMutex
func sync_runtime_SemacquireMutex(addr *uint32, lifo bool, skipframes int) {
semacquire1(addr, lifo, semaBlockProfile|semaMutexProfile, skipframes)
}
其实,可以理解为runtime.go
包里头,runtime_SemacquireMutex
为其声明。实现则为sync.runtime_SemacquireMutex
func runtime_SemacquireMutex(s *uint32, lifo bool, skipframes int)
在阅读golang源码的时候,也可以看到很多go:linkname指令,理解这个指令有助于我们更好的理解golang代码的底层逻辑。
go:linkname格式
//go:linkname localname [importpath.name]
go:linkname指令指示编译器使用 import path.name 作为在源代码中声明为 localname 的变量或函数的目标文件符号名称。第一个参数表示当前方法或变量,第二个参数表示目标方法或变量。由于指令可以破坏类型系统和包模块化,因此在使用时必须导入unsafe
包。
使用条件和注意事项
函数的声明 : func Hello()
添加go编译器指令 go:linkname localname [importpath.name]
这是因为 go build 添加了 -complete 参数来检查完整性。提供.s文件后, 以便编译器绕过 -complete 的检查,允许不完整的函数声明
由于指令可以破坏类型系统和包模块化,因此在使用时必须导入
unsafe
包。
举个🌰
.
├── go.mod
├── demo/internal
│ └── internal.go
│── demo/hello
│ └── hello.go
│ └── hello.s
└── main.go
internal.go
package internal.go
import (
"fmt"
_ "unsafe" // for go:linkname
)
//go:linkname helloPrint demo/hello.Hello
func helloPrint() {
fmt.Println("hello world")
}
hello.go
package hello
import (
_ "demo/internal"
)
func Hello()
main.go
package main
import (
"demo/hello"
)
func main() {
hello.Hello()
}
输出:hello world