2019獨角獸企業重金招聘Python工程師標準>>>
? ? 背景是在寫個日志庫,日志庫有個很重要的功能就是要打印出調用棧,知道具體是哪個文件,哪個函數調用的Info 等。 然后在測試中發現了一種寫法,我自己本機測試一直ok, 但是業務使用的時候調用棧始終不對,打的調用棧少了一層。莫名其妙的,后來對比發現,我們就是go version 不一樣。
? ? go version :
go version go1.9.2 darwin/amd64
? ?go env:
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/didi/Desktop/didi"
GORACE=""
GOROOT="/usr/local/go1.9.2"
GOTOOLDIR="/usr/local/go1.9.2/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/2w/tt1p_4td3yq9xlbl7c2t4jn00000gn/T/go-build427754844=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
我的示例代碼是這樣的:
package mainimport ("fmt""runtime"
)var i []bytetype AAA struct {
}func (a *AAA) test1() *AAA {buf := make([]byte, 1<<20)runtime.Stack(buf, true)fmt.Printf("\n%s", buf)return a
}func (a *AAA) test2() *AAA {i = append(i, "test2"...)return a
}func test() {a := AAA{}a.test1().test2()
}func main() {test()
}
然后呢,我期望的結果:
goroutine 1 [running]:
main.(*AAA).test1(0xc420045f60, 0x1003a4c)/Users/didi/Desktop/didi/src/test/testCall/main.go:15 +0x87
main.test()/Users/didi/Desktop/didi/src/test/testCall/main.go:27 +0x2f
main.main()/Users/didi/Desktop/didi/src/test/testCall/main.go:31 +0x20
但是真實結果是這樣的:
goroutine 1 [running]:
main.(*AAA).test1(0xc42003df48, 0xc42003df70)/Users/didi/Desktop/didi/src/test/testCall/main.go:15 +0x87
main.(*AAA).test2(...)/Users/didi/Desktop/didi/src/test/testCall/main.go:27
main.test()/Users/didi/Desktop/didi/src/test/testCall/main.go:27 +0x2f
main.main()/Users/didi/Desktop/didi/src/test/testCall/main.go:31 +0x20
? ? 問題來了,我日志庫封裝要是有這種類似邏輯,那打印的日志全都是有問題的,怎么可能是test2調用test1? 莫名其妙的。。。
? ? 初步懷疑是內聯引起的問題,這里現象看著很像。編譯,加上不允許內聯后,問題解決,? 解決方式蠻簡單的,函數前加個 // go:noinline。
? ? 為什么會出現這種讓人困惑的現象,通過查看go 官方issue 和 release note? 發現下面解釋:
Users of runtime.Callers should avoid directly inspecting the resulting PC slice and instead use runtime.CallersFrames to get a complete view of the call stack, or runtime.Caller to get information about a single caller. This is because an individual element of the PC slice cannot account for inlined frames or other nuances of the call stack.
// 使用runtime.Caller 不能顯示內聯的細微區別。Specifically, code that directly iterates over the PC slice and uses functions such as runtime.FuncForPC to resolve each PC individually will miss inlined frames. To get a complete view of the stack, such code should instead use CallersFrames. Likewise, code should not assume that the length returned by Callers is any indication of the call depth. It should instead count the number of frames returned by CallersFrames.Code that queries a single caller at a specific depth should use Caller rather than passing a slice of length 1 to Callers.runtime.CallersFrames has been available since Go 1.7, so code can be updated prior to upgrading to Go 1.9.
? ?然后官方有人提了這個issue,?https://github.com/golang/go/issues/22916。總結就是,官方在1.9 的時候覺得1.8及以前版本的不對,Caller 應該將內聯棧也算進去。然后后來大家覺得這種使用不符合習慣,在1.10 又改回去了。我個人試了下,1.10.x, 1.11.x 都是正常的。
? ? 這種問題,大多數人應該遇不上,一個是要求鏈式調用的寫法,第二個得關心調用棧,才會遇到這種奇怪現象。