鍍金池/ 教程/ GO/ 第三部分 附錄
第二部分 源碼
第三部分 附錄
第一部分 語言

第三部分 附錄

A. 工具

1. 工具集

1.1 go build

http://wiki.jikexueyuan.com/project/the-go-study-notes-fourth-edition/images/26.png" alt="" />

gcflags

http://wiki.jikexueyuan.com/project/the-go-study-notes-fourth-edition/images/27.png" alt="" />

ldflags

http://wiki.jikexueyuan.com/project/the-go-study-notes-fourth-edition/images/28.png" alt="" />

更多參數(shù):

go tool 6g -h 或 [https://golang.org/cmd/gc/](https://golang.org/cmd/gc/)
go tool 6l -h 或 [https://golang.org/cmd/ld/](https://golang.org/cmd/ld/)

1.2 go install

和 go build 參數(shù)相同,將生成文件拷貝到 bin、pkg 目錄。優(yōu)先使用 GOBIN 環(huán)境變量所指定目錄。

1.3 go clean

http://wiki.jikexueyuan.com/project/the-go-study-notes-fourth-edition/images/29.png" alt="" />

1.4 go get

下載并安裝擴展包。默認保存到 GOPATH 指定的第一個工作空間。

http://wiki.jikexueyuan.com/project/the-go-study-notes-fourth-edition/images/30.png" alt="" />

1.5 go tool objdump

反匯編可執(zhí)行文件。

$ go tool objdump -s "main\.\w+" test
$ go tool objdump -s "main\.main" test

2. 條件編譯

通過 runtime.GOOS/GOARCH 判斷,或使用編譯約束標記。

// +build darwin linux
                        <--- 必須有空行,以區(qū)別包文檔。
package main

在源文件 (.go, .h, .c, .s 等) 頭部添加 "+build" 注釋,指示編譯器檢查相關(guān)環(huán)境變量。多個約束標記會合并處理。其中空格表示 OR,逗號 AND,感嘆號 NOT。

// +build darwin linux --> 合并結(jié)果 (darwin OR linux) AND (amd64 AND (NOT cgo))
// +build amd64,!cgo

如果 GOOS、GOARCH 條件不符合,則編譯器會會忽略該文件。

還可使用文件名來表示編譯約束,比如 test_darwin_amd64.go。使用文件名拆分多個不同平臺源文件,更利于維護。

$ ls -l /usr/local/go/src/pkg/runtime

-rw-r--r--@ 1 yuhen admin 11545 11 29 05:38 os_darwin.c
-rw-r--r--@ 1 yuhen admin 1382 11 29 05:38 os_darwin.h
-rw-r--r--@ 1 yuhen admin 6990 11 29 05:38 os_freebsd.c
-rw-r--r--@ 1 yuhen admin 791 11 29 05:38 os_freebsd.h
-rw-r--r--@ 1 yuhen admin 644 11 29 05:38 os_freebsd_arm.c
-rw-r--r--@ 1 yuhen admin 8624 11 29 05:38 os_linux.c
-rw-r--r--@ 1 yuhen admin 1067 11 29 05:38 os_linux.h
-rw-r--r--@ 1 yuhen admin 861 11 29 05:38 os_linux_386.c
-rw-r--r--@ 1 yuhen admin 2418 11 29 05:38 os_linux_arm.c
支持:*_GOOS、*_GOARCH、*_GOOS_GOARCH、*_GOARCH_GOOS 格式。

可忽略某個文件,或指定編譯器版本號。更多信息參考標準庫 go/build 文檔。

// +build ignore
// +build go1.2 <--- 最低需要 go 1.2 編譯。

自定義約束條件,需使用 "go build -tags" 參數(shù)。

test.go

// +build beta,debug

package main

func init() {
    println("test.go init")
}

輸出:

$ go build -tags "debug beta" && ./test
test.go init

$ go build -tags "debug" && ./test
$ go build -tags "debug \!cgo" && ./test

3. 跨平臺編譯

首先得生成與平臺相關(guān)的工具和標準庫。

$ cd /usr/local/go/src

$ GOOS=linux GOARCH=amd64 ./make.bash --no-clean

# Building C bootstrap tool.
cmd/dist

# Building compilers and Go bootstrap tool for host, darwin/amd64.

cmd/6l
cmd/6a
cmd/6c
cmd/6g
...
---
Installed Go for linux/amd64 in /usr/local/go
Installed commands in /usr/local/go/bin
說明:參數(shù) no-clean 避免清除其他平臺文件。

然后回到項目所在目錄,設定 GOOS、GOARCH 環(huán)境變量即可編譯目標平臺文件。

$ GOOS=linux GOARCH=amd64 go build -o test

$ file test
learn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV)

$ uname -a
Darwin Kernel Version 12.5.0: RELEASE_X86_64 x86_64

4. 預處理

簡單點說,go generate 掃描源代碼文件,找出所有 "//go:generate" 注釋,提取并執(zhí)行預處理命令。

  • 命令必須放在 .go 文件。
  • 每個文件里可以有多個 generate 指令。
  • 必須顯式用 go generate 執(zhí)行。
  • 命令行支持環(huán)境變量。
  • 按文件名順序依次提取執(zhí)行。
  • 串行執(zhí)行,出錯終止。
  • 必須以 "//go:generate" 開頭,雙斜線后沒有空格。

不屬于 build 組成部分,設計目標是提供給包開發(fā)者使用,因為包用戶可能不具備命令執(zhí)行環(huán)境。

//go:generate ls -l
//go:generate du

http://wiki.jikexueyuan.com/project/the-go-study-notes-fourth-edition/images/31.png" alt="" />

還可定義別名。須提前定義,僅在當前文件內(nèi)有效。

//go:generate -command YACC go tool yacc
//go:generate YACC -o test.go -p parse test.y

可用條件編譯,讓 go build 忽略包含 generate 的文件。

// +build generate

$ go generate -tags generate

資源:Design Document Generating code

B. 調(diào)試

1. GDB

默認情況下,編譯的二進制文件已包含 DWARFv3 調(diào)試信息,只要 GDB 7.1 以上版本都可以調(diào)試。

相關(guān)選項:

  • 調(diào)試: 禁用內(nèi)聯(lián)和優(yōu)化 -gcflags "-N -l"。
  • 發(fā)布: 刪除調(diào)試信息和符號表 -ldflags "-w -s"。

除了使用 GDB 的斷點命令外,還可以使用 runtime.Breakpoint 函數(shù)觸發(fā)中斷。另外,runtime/debug.PrintStack 可用來輸出調(diào)用堆棧信息。

某些時候,需要手工載入 Go Runtime support (runtime-gdb.py)。

.gdbinit

define goruntime
    source /usr/local/go/src/runtime/runtime-gdb.py
end

set disassembly-flavor intel
set print pretty on
dir /usr/local/go/src/pkg/runtime

說明:OSX 環(huán)境下,可能需要以 sudo 方式啟動 gdb。

2. Data Race

數(shù)據(jù)競爭 (data race) 是并發(fā)程序里不太容易發(fā)現(xiàn)的錯誤,且很難捕獲和恢復錯誤現(xiàn)場。Go 運行時內(nèi)置了競爭檢測,允許我們使用編譯器參數(shù)打開這個功能。它會記錄和監(jiān)測運行時內(nèi)存訪問狀態(tài),發(fā)出非同步訪問警告信息。

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    x := 100

    go func() {
        defer wg.Done()

        for {
            x += 1
        }
    }()

    go func() {
        defer wg.Done()
        for {
            x += 100
        }
    }()

    wg.Wait()
}

輸出:

$ GOMAXPROCS=2 go run -race main.go

==================
WARNING: DATA RACE
Write by goroutine 4:
    main.func·002()
        main.go:25 +0x59

Previous write by goroutine 3:
    main.func·001()
        main.go:18 +0x59

Goroutine 4 (running) created at:
    main.main()
        main.go:27 +0x16f
Goroutine 3 (running) created at:
    main.main()
        main.go:20 +0x100
==================

數(shù)據(jù)競爭檢測會嚴重影響性能,不建議在生產(chǎn)環(huán)境中使用。

func main() {
    x := 100

    for i := 0; i < 10000; i++ {
        x += 1
    }

    fmt.Println(x)
}

輸出:

$ go build && time ./test

10100

real" 0m0.060s
user" 0m0.001s
sys" 0m0.003s

$ go build -race && time ./test

10100

real" 0m1.025s
user" 0m0.003s
sys" 0m0.009s

通常作為非性能測試項啟用。

$ go test -race

C. 測試

自帶代碼測試、性能測試、覆蓋率測試框架。

  • 測試代碼必須保存在 *_test.go 文件。
  • 測試函數(shù)命名符合 TestName 格式,Name 以大寫字母開頭。

注: 不要將代碼放在名為 main 的目錄下,這會導致 go test "cannot import main" 錯誤。

1. Test

使用 testing.T 相關(guān)方法決定測試狀態(tài)。

testing.T

http://wiki.jikexueyuan.com/project/the-go-study-notes-fourth-edition/images/32.png" alt="" />

main_test.go

package main

import (
    "testing"
    "time"
)

func sum(n ...int) int {
    var c int
    for _, i := range n {
        c += i
    }

    return c
}

func TestSum(t *testing.T) {
    time.Sleep(time.Second * 2)
    if sum(1, 2, 3) != 6 {
        t.Fatal("sum error!")
    }
}

func TestTimeout(t *testing.T) {
    time.Sleep(time.Second * 5)
}

默認 go test 執(zhí)行所有單元測試函數(shù),支持 go build 參數(shù)。

http://wiki.jikexueyuan.com/project/the-go-study-notes-fourth-edition/images/33.png" alt="" />

$ go test -v -timeout 3s

=== RUN TestSum
--- PASS: TestSum (2.00 seconds)
=== RUN TestTimeout
panic: test timed out after 3s
FAIL" test" 3.044s

$ go test -v -run "(i)sum"

=== RUN TestSum
--- PASS: TestSum (2.00 seconds)
PASS
ok " test" 2.044s

可重寫 TestMain 函數(shù),處理一些 setup/teardown 操作。

func TestMain(m *testing.M) {
    println("setup")
    code := m.Run()
    println("teardown")
    os.Exit(code)
}

func TestA(t *testing.T) {}
func TestB(t *testing.T) {}
func BenchmarkC(b *testing.B) {}

輸出:

$ go test -v -test.bench .

setup
=== RUN TestA
--- PASS: TestA (0.00s)
=== RUN TestB
--- PASS: TestB (0.00s)
PASS
BenchmarkC" 2000000000" 0.00 ns/op
teardown
ok " test" 0.028s

2. Benchmark

性能測試需要運行足夠多的次數(shù)才能計算單次執(zhí)行平均時間。

func BenchmarkSum(b *testing.B) {
    for i := 0; i < b.N; i++ {
        if sum(1, 2, 3) != 6 {
            b.Fatal("sum")
        }
    }
}

默認情況下,go test 不會執(zhí)行性能測試函數(shù),須使用 "-bench" 參數(shù)。

go test

http://wiki.jikexueyuan.com/project/the-go-study-notes-fourth-edition/images/34.png" alt="" />

$ go test -v -bench .

=== RUN TestSum
--- PASS: TestSum (2.00 seconds)
=== RUN TestTimeout
--- PASS: TestTimeout (5.00 seconds)
PASS

BenchmarkSum 100000000 11.0 ns/op

ok " test" 8.358s

$ go test -bench . -benchmem -cpu 1,2,4 -benchtime 30s

BenchmarkSum 5000000000 11.1 ns/op 0 B/op 0 allocs/op
BenchmarkSum-2 5000000000 11.4 ns/op 0 B/op 0 allocs/op
BenchmarkSum-4 5000000000 11.3 ns/op 0 B/op 0 allocs/op

ok " test" 193.246s

3. Example

與 testing.T 類似,區(qū)別在于通過捕獲 stdout 輸出來判斷測試結(jié)果。

func ExampleSum() {
    fmt.Println(sum(1, 2, 3))
    fmt.Println(sum(10, 20, 30))
    // Output:
    // 6
    // 60
}

不能使用內(nèi)置函數(shù) print/println,它們默認輸出到 stderr。

$ go test -v

=== RUN: ExampleSum
--- PASS: ExampleSum (8.058us)
PASS

ok " test" 0.271s

Example 代碼可輸出到文檔,詳情參考包文檔章節(jié)。

4. Cover

除顯示代碼覆蓋率百分比外,還可輸出詳細分析記錄文件。

http://wiki.jikexueyuan.com/project/the-go-study-notes-fourth-edition/images/35.png" alt="" />

go test

$ go test -cover -coverprofile=cover.out -covermode=count

PASS
coverage: 80.0% of statements
ok " test" 0.043s

$ go tool cover -func=cover.out

test.go: Sum 100.0%
test.go: Add 0.0%
total:" (statements) 80.0%

用瀏覽器輸出結(jié)果,能查看更詳細直觀的信息。包括用不同顏色標記覆蓋、運行次數(shù)等。

$ go tool cover -html=cover.out
說明:將鼠標移到代碼塊,可以看到具體的執(zhí)行次數(shù)。

5. PProf

監(jiān)控程序執(zhí)行,找出性能瓶頸。

import (
    "os"
    "runtime/pprof"
)

func main() {
    // CPU
    cpu, _ := os.Create("cpu.out")
    defer cpu.Close()
    pprof.StartCPUProfile(cpu)
    defer pprof.StopCPUProfile()

    // Memory
    mem, _ := os.Create("mem.out")
    defer mem.Close()
    defer pprof.WriteHeapProfile(mem)
}

除調(diào)用 runtime/pprof 相關(guān)函數(shù)外,還可直接用測試命令輸出所需記錄文件。

go test

http://wiki.jikexueyuan.com/project/the-go-study-notes-fourth-edition/images/36.png" alt="" />

以 net/http 包為演示,先生成記錄文件。

$ go test -v -test.bench "." -cpuprofile cpu.out -memprofile mem.out net/http

進入交互式查看模式。

$ go tool pprof http.test mem.out

(pprof) top5
2597.58kB of 2597.58kB total ( 100%)
Dropped 421 nodes (cum <= 12.99kB)
Showing top 5 nodes out of 28 (cum >= 1536.60kB)

     flat  flat%    sum%       cum     cum%
1024.04kB 39.42%  39.42% 1024.04kB   39.42% encoding/asn1.parsePrintableString
 548.84kB 21.13%  60.55%  548.84kB   21.13% mime.setExtensionType
 512.56kB 19.73%  80.28% 1536.60kB   59.16% crypto/x509.parseCertificate
 512.14kB 19.72%    100%  512.14kB   19.72% mcommoninit
      0        0%   100% 1536.60kB   59.16% crypto/tls.(*Conn).Handshake
  • flat: 僅當前函數(shù),不包括其調(diào)用的其他函數(shù)。
  • sum: 列表前幾行所占百分比總和。
  • cum: 當前函數(shù)完整調(diào)用堆棧。

默認輸出 inuse_space,可在命令行指定其他值,包括排序方式。

$ go tool pprof -alloc_space -cum http.test mem.out

可輸出函數(shù)調(diào)用的列表統(tǒng)計信息。

http://wiki.jikexueyuan.com/project/the-go-study-notes-fourth-edition/images/37.png" alt="" />

或者是更詳細的源碼模式。

http://wiki.jikexueyuan.com/project/the-go-study-notes-fourth-edition/images/38.png" alt="" />

除交互模式外,還可直接輸出統(tǒng)計結(jié)果。

$ go tool pprof -text http.test mem.out

2597.58kB of 2597.58kB total ( 100%)
Dropped 421 nodes (cum <= 12.99kB)

     flat  flat%   sum%       cum   cum%
1024.04kB 39.42% 39.42% 1024.04kB 39.42% encoding/asn1.parsePrintableString
 548.84kB 21.13% 60.55%  548.84kB 21.13% mime.setExtensionType
 512.56kB 19.73% 80.28% 1536.60kB 59.16% crypto/x509.parseCertificate
 512.14kB 19.72%   100%  512.14kB 19.72% mcommoninit
      0      0%    100% 1536.60kB 59.16% crypto/tls.(*Conn).Handshake
      0      0%    100% 1536.60kB 59.16% crypto/tls.(*Conn).clientHandshake

輸出圖形文件。

$ go tool pprof -web http.test mem.out

還可用 net/http/pprof 實時查看 runtime profiling 信息。

package main

import (
    _ "net/http/pprof"

    "net/http"
    "time"
)

func main() {
    go http.ListenAndServe("localhost:6060", nil)

    for {
        time.Sleep(time.Second)
    }
}

在瀏覽器中查看 http://localhost:6060/debug/pprof/

附: 自定義統(tǒng)計數(shù)據(jù),可用 expvar 導出,用瀏覽器訪問 /debug/vars 查看。