Try   HackMD

Golang 邁向精通之路

案例分析

卡死案例

package main import ( "fmt" "runtime" ) func main() { var i byte go func() { for i = 0; i <= 255; i++ { } }() fmt.Println("Dropping mic") // Yield execution to force executing other goroutines runtime.Gosched() runtime.GC() fmt.Println("Done") }

涉及以下三個概念

  1. Byte 是什麼
  2. Gorountine 如何調度
  3. Golang GC 時會發生什麼情況
  • Golang 中,Byte 其實被 alias 到 uint8 上,所以for迴圈永遠成立,因為 i++ 到 i = 255 時會溢出,因此 i <= 255 一定成立
  • Go scheduler 除了在一个 goroutine 執行結束時會調度後面的goroutine 執行,還會在正被執行的 goroutine 發生以下情況時讓出當前 goroutine 的執行權,並且調度後面的 goroutine 執行:
    1. IO 操作
    2. Channel 阻塞
    3. System call
    4. 運行較長時間

基礎篇

不限定參數

func TestArgs(frist int, arg ...interface{}) { //...Do something }

為什麼要使用 range

  • 意義明確,美觀

  • range 左邊的循環變數可以用以下方式來賦值

    • (:=) 每次循環的迭代會重用宣告的變數
  • 表達式左邊必須是可以尋址的或者是 map 索引表達式,如果表達式是 channel 只允許一個變數

Range 概念

  • 所有類型的 Range 本質上都是 C 風格的循環(for)
  • 遍歷到的值都會被賦值給一個臨時變數
  • 循環變數在每一次迭代中都會被賦值並且會重複使用
  • 可以在迭代過程中移除一個 Map 裡的元素或者向 Map 中添加元素,添加的元素並不一定會在後續迭代中被遍歷到。

Range 表達式

range 右邊表達式的結果,可以是以下資料型別

  • Array
  • Pointer to an array
  • Slice
  • String
  • Map
  • channel permitting receive operations 比如:chan int or chan<- int

Range 範例

package main import "fmt" func main() { nums := []int{2, 3, 4} sum := 0 for _, num := range nums { sum += num } fmt.Println("sum:", sum) for i, num := range nums { if num == 3 { fmt.Println("index:", i) } } kvs := map[string]string{"a": "apple", "b": "banana"} for k, v := range kvs { fmt.Printf("%s -> %s\n", k, v) } for k := range kvs { fmt.Println("key:", k) } for i, c := range "go" { fmt.Println(i, c) } }

輸出:

$ go run range.go
sum: 9
index: 1
a -> apple
b -> banana
key: a
key: b
0 103
1 111

重點 range 範例

type Foo struct { bar string } func main() { list := []Foo{ {"A"}, {"B"}, {"C"}, } list2 := make([]*Foo, len(list)) for i, value := range list { list2[i] = &value } fmt.Println(list[0], list[1], list[2]) fmt.Println(list2[0], list2[1], list2[2]) }

輸出:

// 實際輸出
{A} {B} {C}
&{C} &{C} &{C}

// 預期輸出
{A} {B} {C}
&{A} &{B} &{C}

根據以上的範例可以得知 Go 的 forrange 始終是值複製方式代替被遍歷的元素本身,簡而言之就是 range 中那個 value 是一個值拷貝,而不是元素本身當我們期望用 &value 獲得元素地址時,實際上只是取到了 value 這個臨時變數的地址,而非 List 中真正被遍歷到的某個元素的地址所以,在上面例子中 list2 被填充了三個相同的地址其實都是 value 的地址,而在最後一次循環中 value 被賦值為=={c}==,因此 list2 輸出時的時候顯示為 &{c}

正確的寫法如下:

for i, _ := range list { list2[i] = &list[i] }

這樣輸出list2中的元素,就能得到想到的結果

Array 是值類型

//example 1 func printArray(arr []int) { } //example 2 func printArray(arr [10]int) { }
  • 上述 example 2傳入的arr是拷貝
  • 上述 example 1傳入的是slice

Slice

一般 Golang 最常使用 Slice

arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7} func main() { arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7} s := arr[2:6] fmt.Println("arr[2:6] = ", arr[2:6]) fmt.Println("arr[:6] = ", arr[:6]) fmt.Println("arr[2:] = ", arr[2:]) fmt.Println("arr[:] = ", arr[:]) } //arr[2:6] = [2 3 4 5] //arr[:6] = [0 1 2 3 4 5] //arr[2:] = [2 3 4 5 6 7] //arr[:] = [0 1 2 3 4 5 6 7]

上述的 s 就是一個Slice(切片)

  • Slice 本身沒有資料,是對底層Array的一個View
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7} s := arr[2:6] s[0] = 10 //arr -> [0, 1, 10, 3, 4, 5, 6, 7]

slice 擴展

  • 因此 Slice 可以向後擴展,不可以向前擴展
  • s[i] 不可以超越 len[s],向後擴展不可以超越底層Array的cap(s)

slice 操作

向 slice 添加元素

s3 := append(s2, 10) s4 := append(s3, 11) s5 := append(s4, 12)
  • 添加元素時如果超越cap,系統會重新分配更大的底層Array
  • 由於值傳遞的關係,必須接收 append 的返回值
  • s = append(s, val)

Map

Map 遍歷特性

  • 使用 range 遍歷 key, 或者遍歷 key, value對
  • 不保證遍歷順序,如需要順序,需要手動對Key排序,再拿排序後的Slice進行遍歷
  • 使用 len 可以獲得元素個數

Map 的 key

  • map 使用 hash table,必須可以比較相等
  • 除了 slice, map ,func 其他的內建型別都可以做為key
  • Struct 型別不包含上述限制,也可以作為key

尋找最長不含有重複字的字串

規則:

  1. abcabcbb -> abc
  2. bbbbb -> b
  3. pwwkep -> wke

Rune 字串特性

字節 => 位元組 => Byte => 一個位元組代表八個位元

  • Rune 相當於其他語言的 char
  • 使用 utf8.RuneCountInString 獲得字串數量
  • 使用 len 獲得字節長度
  • 使用 []byte 獲得字節

Struct

  • Struct 是由一组field组成,每個field包括了名字(可選)和類型

  • 為結構定義方法

    ​​​​func (node TreeNode) print() { ​​​​ fmt.Print(node.value) ​​​​} ​​​​ ​​​​func (node *TreeNode) setValue(value) { ​​​​ node.value = value ​​​​}
    • 顯示定義為命名方法的定義
    • 只有使用指針才可以改變內容
    • nil 指針也可以調用方法
  • 值接收者 vs 指針接收者

    • 要改變內容必須使用指針接收者
    • 結構過大也可以考慮指針接收者
    • 一致性:如有指針接收者,最好都是指針接收者
    • 值接收者是Go特有

封裝

  • 名字一般使用CamelCase
  • 首字母大寫: Public
  • 首字母小寫: Private

Package

  • 每個目錄一個 Package
  • 為結構定義的方法必須放在同一個 Package 內
  • 可以是不同文件
  • 多個.go檔在main包底下是無法互相使用的
  • 要導出 package 切記首字母要大寫!!包括(
    )很重要!!!

package import 原理

  • 如果一個 main 導入其他的包,包會被順序導入
  • 如果導入的包(pk1)依賴其他的包(pk2),會首先導入pk2然後初始化,pk2中的常數與變數,如果pk2中有 init 函數,會自動執行 init
  • 所有包導入完成後,才會對 main 的常數和變數進行初始化,然後執行 main 中的 init 函數(如果有)最後執行 main 函數
  • 如果一個包被導入多次,實際上只會導入一次

import 別名

import iii "fmt" // 此处省略一些代码... iii.Println("hello")
import . "fmt" // 此处省略一些代码... Println("hello") :::info 有一個最特别的情況,如果别名是_的話,表示只註冊該包(初始化全局常數和變數,且執行其init函数),並不會實際引入該包。 :::

擴充已有的 Package

  • 定義別名
  • 使用合成(組合)

Ducking type

  • 介面(接口/interface)由使用者來定義

  • 介面的實現是隱式讀

  • 只要實現介面裡的方法

  • 介面包括引用(T,type)&值(v,value) or

  • 介面(接口)裡面有什麼

    • 接口變數自帶指針
    • 接口變數同樣採用值傳遞,幾乎不需要使用接口的指針
    • 指針接收者實現只能以指針方式使用,值接收者則都可
  • 將func接收者或回傳者改為interface{}就可接受任何類型的變數

  • 強制轉型 head.(int) 拿出的值轉為 int

  • 系統接口組合

函數式&閉包

func funcName(input1 type1, intput2 type2) (output1 type1, output2 type2) { return value1, value2 }
  • 函數是一等公民:參數,變數,返回值都可以是函數

  • 高階函數

  • 函數 -> 閉包

  • 函數也可以實現接口

  • 函數實現範例

  • Go 語言閉包的應用

    • 更自然,不需要修飾如何訪問自由變數
    • 沒有 Lambda 表達式,但是有匿名函式
  • 函數也可以通過宣告方式作為一種type
    type addNum func(int, int) int

函數的參數

Defer

正統的函數式

  • 不可變性:不能有狀態,只有常數和函數
  • 函數只能有一個參數

何時使用 Defer

  • Open/Close
  • Lock/Unlock
  • PrintHeader/PrintFooter

錯誤處理

Panic & Recover

Panic 特性

  • 停止當前函數執行
  • 一直向上返回,執行每一層的 Defer
  • 如果沒有遇到 Recover,程式退出

Recover 特性

  • 僅在 Defer 調用中使用
  • 獲取 Panic 的值
  • 如果無法處理,可重新 Panic

flag & pflag

flag

  • 將flag綁定到一個變數

    import "flat" ​ ​var flagvar intfunc init() { ​ flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")}
  • 綁定自定義的類型

    import "flag"// 自定義類型需要實做 value 接口 ​flag.Var(&flagVal, "name", "help message for flagname")
  • flag 解析

    // 解析函數將會在碰到第一個非flag命令行參數時停止 ​flag.Parse()
  • 命令行參數的格式

    ​-flag xxx (使用空格,一個 - 符號) ​––flag xxx (使用空格,兩個 - 符號) ​-flag=xxx (使用等號,一個 - 符號) ​––flag=xxx (使用等號,兩個 - 符號)
  • flag 範例

    package main ​import ("flag""fmt") ​ ​var vehicleName = flag.String("name", "porsche", "Input the vehicle name.")var vehicleYear = flag.Int("year", 10, "Input the vehicle year") ​ ​// func Args() []string// Args returns the non-flag coomand-line arguments.// func NArg() int// NArg is the number of arguments remaining after flags have been processed.func main() { ​ flag.Parse() ​ fmt.Printf("args=%s, num=%d\n", flag.Args(), flag.NArg())for i := 0; i != flag.NArg(); i++ { ​ fmt.Printf("arg[%d]=%s\n", i, flag.Arg(i))} ​ ​ fmt.Println("vehicle name=", *vehicleName) ​ fmt.Println("vehicle year=", *vehicleYear)}

    執行

    ​$ go run main.go -h ​Usage of /var/folders/xf/hcn0238145n2w4cp_vk9vtcw0000gn/T/go-build233989185/b001/exe/main: ​ -name string ​ Input the vehicle name. (default "porsche") ​ -year int ​ Input the vehicle year (default 10) ​exit status 2 ​----------------------------------------------------- ​$ go run main.go name=toyota year=2019 ​args=[name=toyota year=2019], num=2 ​arg[0]=name=toyota ​arg[1]=year=2019 ​vehicle name= porsche ​vehicle year= 10

pflag

基本上使用與flag基本相同
新增功能:

  • 增加 shorthand 參數

    // func IntP(name, shorthand string, value int, usage string) *int// IntP is like Int, but accepts a shorthand letter that can be used after a single dash.var ip= flag.IntP("flagname", "f", 1234, "help message")
  • 設定非必要選項的預設值

    var ip = flag.IntP("flagname", "f", 1234, "help message") ​flag.Lookup("flagname").NoOptDefVal = "4321"

    結果如下圖:

    Parsed Arguments Resulting Value
    -flagname=1357 ip=1357
    -flagname ip=4321
    [nothing] ip=1234
  • flag 客製化
    例如希望使用-_或者.,像--my-flag == --my_flag == --my.flag:

    func wordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName { ​ from := []string{"-", "_"} ​ to := "."for _, sep := range from { ​ name = strings.Replace(name, sep, to, -1)}return pflag.NormalizedName(name)} ​myFlagSet.SetNormalizeFunc(wordSepNormalizeFunc)

    例如希望聯合兩個參數,像--old-flag-name == --new-flag-name:

    func aliasNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {switch name {case "old-flag-name": ​ name = "new-flag-name"break}return pflag.NormalizedName(name)} ​myFlagSet.SetNormalizeFunc(aliasNormalizeFunc)

    例如希望棄用名叫badflag參數,並告知開發者使用代替參數:

    // deprecate a flag by specifying its name and a usage message ​flags.MarkDeprecated("badflag", "please use --good-flag instead")

    例如希望保持使用noshorthandflag,但想棄用簡稱n

    // deprecate a flag shorthand by specifying its flag name and a usage message ​flags.MarkShorthandDeprecated("noshorthandflag", "please use --noshorthandflag only")

    隱藏flag

    // hide a flag by specifying its name ​flags.MarkHidden("secretFlag")

    例如希望關閉對help文件或使用說明的flag排序:

    ​flags.BoolP("verbose", "v", false, "verbose output") ​flags.String("coolflag", "yeaah", "it's really cool flag") ​flags.Int("usefulflag", 777, "sometimes it's very useful") ​flags.SortFlags = false ​flags.PrintDefaults()

    輸出:

    ​-v, --verbose verbose output ​​​​--coolflag string it's really cool flag (default "yeaah") ​​​​--usefulflag int sometimes it's very useful (default 777)

Tag

當處理JSON格式的時候,經常會看到宣告 Struct 結構如下:

type User struct { UserId int `json:"user_id" bson:"user_id"` UserName string `json:"user_name" bson:"user_name"` }

理解此設計首先要先理解Golang命名採用駝峰(TestOne)方式
將上面定義的User轉成JSON格式:

u := &User{UserId: 1, UserName: "tony"} j, _ := json.Marshal(u) fmt.Println(string(j)) // 輸出内容:{"user_id":1,"user_name":"tony"}

如果未在屬性中增加標籤說明,則輸出:

{"UserId":1,"UserName":"tony"}
  • 標籤解釋
type T struct { f1 string "f one" f2 string f3 string `f three` f4, f5 int64 `f four and five` }

不管是raw string還是interpreted string都可以用來當tag。 如果field定義時候兩個名字共用一個屬性,那麼這個tag會被附在兩個名字上,像f4,f5一樣。

  • 反射(reflect)
    Tag在執行時可以通過reflection擴充包來讀取
package main import ( "fmt" "reflect" ) type T struct { f1 string "f one" f2 string f3 string `f three` f4, f5 int64 `f four and five` } func main() { t := reflect.TypeOf(T{}) f1, _ := t.FieldByName("f1") fmt.Println(f1.Tag) // f one f4, _ := t.FieldByName("f4") fmt.Println(f4.Tag) // f four and five f5, _ := t.FieldByName("f5") fmt.Println(f5.Tag) // f four and five }

設定一个空tag和不設定tag的效果一致

type T struct { f1 string `` f2 string } func main() { t := reflect.TypeOf(T{}) f1, _ := t.FieldByName("f1") fmt.Printf("%q\n", f1.Tag) // "" f2, _ := t.FieldByName("f2") fmt.Printf("%q\n", f2.Tag) // "" }
  • 格式

Tags可以由鍵值對來组成,通過空格符號来分割鍵值 —key1:"value1" key2:"value2" key3:"value3"。如果Tags格式沒問題的化,我们可以通過Lookup或者Get來獲取鍵值對的值。
Lookup回傳兩個值 —

type T struct { f string `one:"1" two:"2"blank:""` } func main() { t := reflect.TypeOf(T{}) f, _ := t.FieldByName("f") fmt.Println(f.Tag) // one:"1" two:"2"blank:"" v, ok := f.Tag.Lookup("one") fmt.Printf("%s, %t\n", v, ok) // 1, true v, ok = f.Tag.Lookup("blank") fmt.Printf("%s, %t\n", v, ok) // , true v, ok = f.Tag.Lookup("five") fmt.Printf("%s, %t\n", v, ok) // , false }

Get方法只是簡單的包装了以下Lookup。但是丟棄了是否成功结果

func (tag StructTag) Get(key string) string { v, _ := tag.Lookup(key) return v }
  • 轉化
    將結構體的值轉化成其他的類型,可通過Tag來定義
type T1 struct { f int `json:"foo"` } type T2 struct { f int `json:"bar"` } t1 := T1{10} var t2 T2 t2 = T2(t1) fmt.Println(t2) // {10}
  • go vet
    go的編譯器不會强行要求你使用合理的tags。但是 go vet可以檢查出你的tag是否合理。
package main type T struct { f string "one two three" } func main() {} > go vet tags.go tags.go:4: struct field tag `one two three` not compatible with reflect.StructTag.Get: bad syntax for struct tag pair

func init()

  • Init 函數的主要作用:

    • 初始化不可修改的參數
    • 程式執行前的註冊
    • 實現 sync.Once 功能
    • Other
  • Init 函數的主要特性:

    • init 函數優先於 main 函數自動執行,不能被其他函數呼叫
    • init 函數沒有輸入參數、返回值
    • 每個package可以有多個 init 函數
    • package 的每個文件也可以有多個 init 函數
    • 通一個 package 的 init 執行順序,golang沒有明確定義,因此請勿依賴排序
    • 不同 package init的函數依照 package 導入的依賴關係決定執行順序
  • Golang 程式初始化
    Golang 程式初始化優先於 main 函數執行,由 runtime 進行初始化,初始化順去如下:

    1. 初始化導入的 package (package的初始化順序並不是按照導入順序執行的),runtime需要解析 package 依賴關係,沒有依賴的 package 最先初始化,與變數初始化依賴關係類似
    2. 初始化 package 作用域的變數(該作用域的變數之初始化也並非按照從上到下從左到右的順序,runtime解析變數依賴關係,沒有依賴關係的最先初始化
    3. 執行 package init函數
  • 範例1:
    main.go

    package main import ("fmt")var T int64 = a()func init() { ​ fmt.Println("init in main.go ")}func a() int64 { ​ fmt.Println("calling a()")return 2}func main() { ​ fmt.Println("calling main")}

    輸出:

    ​calling a()
    ​init in main.go
    ​calling main
    

    初始化順序: 變數初始化 -> init() -> main()

  • 範例2:
    pack.go

    package pack ​import ("fmt""test_util")var Pack int = 6func init() { ​ a := test_util.Util ​ fmt.Println("init pack ", a)}

    test_util.go

    package test_util ​import "fmt"var Util int = 5func init() { ​ fmt.Println("init test_util")}

    main.go

    package main ​import ("fmt""pack""test_util")func main() { ​ fmt.Println(pack.Pack) ​ fmt.Println(test_util.Util)}

    輸出:

    ​init test_util
    ​init pack  5
    ​6
    ​5
    

    初始化順序:test_util -> pack
    由於 pack package 的初始化依賴 test_util,因此運行時先初始化 test_util 在初始化 pack package

  • 範例3:
    sandbox.go

    package main ​import "fmt"var _ int64 = s()func init() { ​ fmt.Println("init in sandbox.go")}func s() int64 { ​ fmt.Println("calling s() in sandbox.go")return 1}func main() { ​ fmt.Println("main")}

    a.go

    package main ​import "fmt"var _ int64 = a()func init() { ​ fmt.Println("init in a.go")}func a() int64 { ​ fmt.Println("calling a() in a.go")return 2}

    z.go

    package main ​import "fmt"var _ int64 = z()func init() { ​ fmt.Println("init in z.go")}func z() int64 { ​ fmt.Println("calling z() in z.go")return 3}

    輸出:

    ​calling a() in a.go
    ​calling s() in sandbox.go
    ​calling z() in z.go
    ​init in a.go
    ​init in sandbox.go
    ​init in z.go
    ​main
    

    同一個 package 不同 source file文件的 init 函數執行順序,golang spec 沒說明,以上程式輸出來看,執行順序是照source file名稱的字典排序(a-z)

  • 範例4:
    main.go

    package main ​import "fmt"func init() { ​ fmt.Println("init")}func main() {init()}

    init 函數不可以被調用,上面範例會提示:undefined: init

  • 範例5:
    main.go

    package main ​import "fmt"func init() { ​ fmt.Println("init 1")}func init() { ​ fmt.Println("init 2")}func main() { ​ fmt.Println("main")}

    輸出:

    ​init 1
    ​init 2
    ​main
    

    init 函數比較特殊,可以在同一個 package 裡面被多次定義

  • 範例6:
    main.go

    var initArg [20]intfunc init() { ​ initArg[0] = 10for i := 1; i < len(initArg); i++ { ​ initArg[i] = initArg[i-1] * 2}}

    init 函數主要的用途:初始化不能使用初始化表達式初始化的變數

  • 範例7:
    import _ "net/http/pprof"

    golang 對沒有使用的 pakcage 會編譯報錯,但有時候只想調用該 package 的 init函數,不使用pakcage導出的變數或是方法,因此就採用上面的導入方案

變數的初始化

  • 範例1:

    package main ​import "fmt"var ( ​ a int = b + 1 ​ b int = 1)func main() { ​ fmt.Println(a) ​ fmt.Println(b)}
    package main ​import "fmt"func main() {var ( ​ a int = b + 1 ​ b int = 1) ​ fmt.Println(a) ​ fmt.Println(b)}

    輸出:
    上述1

    ​1
    ​2
    

    上述2

    輸出不同的原因:不同作用域類型的變數初始化順序不同
    上述2中的變數a,b是函數作用域內的局部變數,初始化順序為:從左到右、從上到下

  • 範例2:

    func f() int { ​​​​ fmt.Println("f") ​​​​return 1}func g() int { ​ fmt.Println("g")return 2}func h() int { ​ fmt.Println("h")return 3}func main() {var ( ​ a int = f() ​ b int = g() ​ c int = h()) ​ fmt.Println(a, b, c)}

    輸出:

    ​f
    ​g
    ​h
    ​1 2 3
    

    但是對於範例1中package級別的變數,初始化順序與初始化依賴關係有關

  • 範例3:

    package main ​import "fmt"var ( ​ a = c — 2 ​ b = 2 ​ c = f())func f() int { ​ fmt.Printf("inside f and b = %d\n", b)return b + 1}func main() { ​ fmt.Println(a) ​ fmt.Println(b) ​ fmt.Println(c)}

    上述初始化的順序:

    1. b在第一個初始化週期被初始化,因為變數b不依賴任何其他變數
    2. c在第二個初始化週期被初始化,因為a的初始化依賴c
    3. a在第三個初始化週期被初始化

    輸出:

    ​inside f and b = 2
    ​1
    ​2
    ​3
    

    在每一個初始化週期,執行時會挑選一個沒有任何依賴的變數初始化,該過程一直持續到所有的變數均被初始化或者出現依賴嵌套的情況:

    package main ​import "fmt"var ( ​ a = b ​ b = c ​ c = f())func f() int {return a ​}func main() { ​ fmt.Println(a, b, c)}

    上述範例編譯器會提示錯誤:initialization loop

    同一個package下多個文件的變數初始化依賴也遵循相同的規則:

    tools.go

    package main ​import "fmt"var ( ​ a = c — 2 ​ b = 2)func main() { ​ fmt.Println(a) ​ fmt.Println(b) ​ fmt.Println(c)}

    utils.go

    package main ​var c = f()func f() int {return b + 1}

    輸出:

    ​1
    ​2
    ​3
    

表格驅動測試

  • 分離測試資料&測試邏輯
  • 明確的出錯訊息
  • 可以部分失敗
  • Go 語言語法更容易實現表格驅動測試

Test 注意點

  • 檔名為(fn_name)_test.go
  • 測試 func -> func Test[fn_name](t *testing.T)

相關範例學習

forever run

  • select

    package main ​import ("fmt""time")func main() {go forever()select {} // block forever}func forever() {for { ​ fmt.Printf("%v+\n", time.Now()) ​ time.Sleep(time.Second)}}
  • Go's runtime package

    package main ​import ("fmt""runtime""time")func main() {go func() { ​ time.Sleep(time.Second) ​ fmt.Println("Go 1")}()go func() { ​ time.Sleep(time.Second * 2) ​ fmt.Println("Go 2")}() ​ runtime.Goexit() ​ fmt.Println("Exit")}
  • channel

    //example 1// Receives the change in the number of goroutinesvar goroutineDelta = make(chan int)func main() {go forever() ​ numGoroutines := 0for diff := range goroutineDelta { ​ numGoroutines += diff ​ if numGoroutines == 0 { os.Exit(0) }}}// Conceptual codefunc forever() {for {if needToCreateANewGoroutine {// Make sure to do this before "go f()", not within f() ​ goroutineDelta <- +1go f()}}}
    // example 2package main ​import ("fmt""time")func main() { ​ done := make(chan bool)go forever()<-done // Block forever}func forever() {for { ​ fmt.Printf("%v+\n", time.Now()) ​ time.Sleep(time.Second)}}
  • Go sync.WaitGroup

    var wg sync.WaitGroup ​func main() {// Create at least 1 goroutine ​ wg.Add(1)go f()go forever() ​ wg.Wait()}// Conceptual codefunc forever() {for {if needToCreateANewGoroutine { ​ wg.Add(1)go f()}}}func f() {// When the termination condition for this goroutine is detected, do: ​ wg.Done()}

參照表

資源

卡死問題
Golang forever run
Golang block forever
Golang format
(未讀)好文
(未讀)Golang source code分析
(未讀)分散式Broker架構

flag參考文件
Tag解釋英
Tag解釋中

golang init
golang goroutine 如何實現
goroutine 與調度器
scalable go scheduler design
goroutine模式
關於range
package 注意