# Golang 邁向精通之路 ## 案例分析 ### 卡死案例 ```go= 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. 運行較長時間 ## 基礎篇 ### 不限定參數 ```go= 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 範例 ```go= 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 範例 ```go= 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 的 for...range 始終是值複製方式代替被遍歷的元素本身,簡而言之就是 range 中那個 value 是一個值拷貝,而不是元素本身當我們期望用 ==&value== 獲得元素地址時,實際上只是取到了 value 這個臨時變數的地址,而非 List 中真正被遍歷到的某個元素的地址所以,在上面例子中 list2 被填充了三個相同的地址其實都是 value 的地址,而在最後一次循環中 value 被賦值為=={c}==,因此 list2 輸出時的時候顯示為 ==&{c}== 正確的寫法如下: ```go= for i, _ := range list { list2[i] = &list[i] } ``` 這樣輸出list2中的元素,就能得到想到的結果 ### Array 是值類型 ```go= //example 1 func printArray(arr []int) { } //example 2 func printArray(arr [10]int) { } ``` * 上述 example 2傳入的arr是拷貝 * 上述 example 1傳入的是slice ### Slice 一般 Golang 最常使用 Slice ```go= 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 ```go= 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 擴展 ![](https://i.imgur.com/rXhkDp2.png) * 因此 Slice 可以向後擴展,不可以向前擴展 * s[i] 不可以超越 len[s],向後擴展不可以超越底層Array的cap(s) slice 操作 向 slice 添加元素 ```go= 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包括了名字(可選)和類型 * 為結構定義方法 ```go= 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 切記首字母要大寫!!包括(![](https://i.imgur.com/TNzDOas.png) )**很重要!!!** #### package import 原理 * 如果一個 `main` 導入其他的包,包會被順序導入 * 如果導入的包(pk1)依賴其他的包(pk2),會首先導入pk2然後初始化,pk2中的常數與變數,如果pk2中有 init 函數,會自動執行 init * 所有包導入完成後,才會對 main 的常數和變數進行初始化,然後執行 main 中的 init 函數(如果有)最後執行 main 函數 * 如果一個包被導入多次,實際上只會導入一次 ![](https://i.imgur.com/GD0JHor.jpg) #### import 別名 ```go= import iii "fmt" // 此处省略一些代码... iii.Println("hello") ``` ```go= import . "fmt" // 此处省略一些代码... Println("hello") :::info 有一個最特别的情況,如果别名是_的話,表示只註冊該包(初始化全局常數和變數,且執行其init函数),並不會實際引入該包。 ::: ``` ### 擴充已有的 Package * 定義別名 * 使用合成(組合) ![](https://i.imgur.com/nGewZLc.png) ![](https://i.imgur.com/A2npJ73.png) ### Ducking type * 介面(接口/interface)由使用者來定義 * 介面的實現是隱式讀 * 只要實現介面裡的方法 * 介面包括引用(T,type)&值(v,value) or ![](https://i.imgur.com/RIyibyF.png) ![](https://i.imgur.com/7Y4ftNl.png) * 介面(接口)裡面有什麼 * 接口變數自帶指針 * 接口變數同樣採用值傳遞,幾乎不需要使用接口的指針 * 指針接收者實現只能以指針方式使用,值接收者則都可 * 將func接收者或回傳者改為`interface{}`就可接受任何類型的變數 * 強制轉型 `head.(int)` 拿出的值轉為 **int** * 系統接口組合 ![](https://i.imgur.com/x3UYF0Z.png) ### 函數式&閉包 :::info ```go= func funcName(input1 type1, intput2 type2) (output1 type1, output2 type2) { return value1, value2 } ``` ::: * 函數是一等公民:參數,變數,返回值都可以是函數 * 高階函數 * 函數 -> 閉包 * 函數也可以實現接口 ![](https://i.imgur.com/weA0eZY.png) * 函數實現範例 ![](https://i.imgur.com/vljiMch.png) ![](https://i.imgur.com/OxnjKEK.png) * 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 ![](https://i.imgur.com/AQfjkXt.png) ### flag & pflag #### flag * 將flag綁定到一個變數 ```go= import "flat" var flagvar int func init() { flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname") } ``` * 綁定自定義的類型 ```go= import "flag" // 自定義類型需要實做 value 接口 flag.Var(&flagVal, "name", "help message for flagname") * flag 解析 ```go= // 解析函數將會在碰到第一個非flag命令行參數時停止 flag.Parse() ``` * 命令行參數的格式 ```bash= -flag xxx (使用空格,一個 - 符號) ––flag xxx (使用空格,兩個 - 符號) -flag=xxx (使用等號,一個 - 符號) ––flag=xxx (使用等號,兩個 - 符號) ``` * flag 範例 ```go= 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) } ``` 執行 ```bash= $ 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 參數 ```go= // 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") ``` * 設定非必要選項的預設值 ```go= 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`: ```go= 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`: ```go= 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`參數,並告知開發者使用代替參數: ```go= // deprecate a flag by specifying its name and a usage message flags.MarkDeprecated("badflag", "please use --good-flag instead") ``` 例如希望保持使用`noshorthandflag`,但想棄用簡稱`n`: ```go= // deprecate a flag shorthand by specifying its flag name and a usage message flags.MarkShorthandDeprecated("noshorthandflag", "please use --noshorthandflag only") ``` 隱藏flag ```go= // hide a flag by specifying its name flags.MarkHidden("secretFlag") ``` 例如希望關閉對help文件或使用說明的flag排序: ```go= 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() ``` 輸出: ```bash= -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 結構如下: ```go= type User struct { UserId int `json:"user_id" bson:"user_id"` UserName string `json:"user_name" bson:"user_name"` } ``` 理解此設計首先要先理解Golang命名採用駝峰(TestOne)方式 將上面定義的User轉成JSON格式: ```go= u := &User{UserId: 1, UserName: "tony"} j, _ := json.Marshal(u) fmt.Println(string(j)) // 輸出内容:{"user_id":1,"user_name":"tony"} ``` 如果未在屬性中增加標籤說明,則輸出: ```json= {"UserId":1,"UserName":"tony"} ``` * 標籤解釋 ```go= 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==擴充包來讀取 ```go= 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的效果一致 ```go= 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回傳兩個值 — ```go= 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==。但是丟棄了是否成功结果 ```go= func (tag StructTag) Get(key string) string { v, _ := tag.Lookup(key) return v } ``` * 轉化 將結構體的值轉化成其他的類型,可通過Tag來定義 ```go= 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是否合理。 ```go= 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 ```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 ```go= package pack import ( "fmt" "test_util" ) var Pack int = 6 func init() { a := test_util.Util fmt.Println("init pack ", a) } ``` test_util.go ```go= package test_util import "fmt" var Util int = 5 func init() { fmt.Println("init test_util") } ``` main.go ```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 ```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 ```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 ```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 ```go= package main import "fmt" func init() { fmt.Println("init") } func main() { init() } ``` init 函數不可以被調用,上面範例會提示:==undefined: init== * 範例5: main.go ```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 ```go= var initArg [20]int func init() { initArg[0] = 10 for i := 1; i < len(initArg); i++ { initArg[i] = initArg[i-1] * 2 } } ``` init 函數主要的用途:初始化不能使用初始化表達式初始化的變數 * 範例7: `import _ "net/http/pprof"` golang 對沒有使用的 pakcage 會編譯報錯,但有時候只想調用該 package 的 init函數,不使用pakcage導出的變數或是方法,因此就採用上面的導入方案 ### 變數的初始化 * 範例1: ```go= package main import "fmt" var ( a int = b + 1 b int = 1 ) func main() { fmt.Println(a) fmt.Println(b) } ``` ```go= package main import "fmt" func main() { var ( a int = b + 1 b int = 1 ) fmt.Println(a) fmt.Println(b) } ``` 輸出: 上述1 ``` 1 2 ``` 上述2 ![](https://i.imgur.com/6O5Gs48.png) 輸出不同的原因:不同作用域類型的變數初始化順序不同 上述2中的變數a,b是函數作用域內的局部變數,==初始化順序為:從左到右、從上到下== * 範例2: ```go= 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: ```go= 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 ``` 在每一個初始化週期,執行時會挑選一個沒有任何依賴的變數初始化,該過程一直持續到所有的變數均被初始化或者出現依賴嵌套的情況: ```go= 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 ```go= package main import "fmt" var ( a = c — 2 b = 2 ) func main() { fmt.Println(a) fmt.Println(b) fmt.Println(c) } ``` utils.go ```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)` ![](https://i.imgur.com/01rBeEk.png) ## 相關範例學習 ### forever run * select ```go= 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 ```go= 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 ```go= //example 1 // Receives the change in the number of goroutines var goroutineDelta = make(chan int) func main() { go forever() numGoroutines := 0 for diff := range goroutineDelta { numGoroutines += diff if numGoroutines == 0 { os.Exit(0) } } } // Conceptual code func forever() { for { if needToCreateANewGoroutine { // Make sure to do this before "go f()", not within f() goroutineDelta <- +1 go f() } } } ``` ```go= // example 2 package 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 ```go= var wg sync.WaitGroup func main() { // Create at least 1 goroutine wg.Add(1) go f() go forever() wg.Wait() } // Conceptual code func forever() { for { if needToCreateANewGoroutine { wg.Add(1) go f() } } } func f() { // When the termination condition for this goroutine is detected, do: wg.Done() } ``` ## 參照表 ## 資源 [卡死問題](https://zhuanlan.zhihu.com/p/44851211) [Golang forever run](https://stackoverflow.com/questions/9543835/how-best-do-i-keep-a-long-running-go-program-running) [Golang block forever](https://blog.sgmansfield.com/2016/06/how-to-block-forever-in-go/) [Golang format](https://blog.csdn.net/tianlongtc/article/details/80166097) (未讀)[好文](https://zhuanlan.zhihu.com/p/26972862) (未讀)[Golang source code分析](https://www.ctolib.com/changkun-go-under-the-hood.html) (未讀)[分散式Broker架構](http://techlog.cn/article/list/10183002) [flag參考文件](https://o-my-chenjian.com/2017/09/20/Using-Flag-And-Pflag-With-Golang/) [Tag解釋英](https://medium.com/golangspec/tags-in-golang-3e5db0b8ef3e) [Tag解釋中](https://studygolang.com/articles/14469) [golang init](https://zhuanlan.zhihu.com/p/34211611) [golang goroutine 如何實現](https://www.zhihu.com/question/20862617) [goroutine 與調度器](http://skoo.me/go/2013/11/29/golang-schedule?utm_campaign=studygolang.com&utm_medium=studygolang.com&utm_source=studygolang.com) [scalable go scheduler design](https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sLKhJYD0Y_kqxDv3I3XMw/edit#heading=h.mmq8lm48qfcw) [goroutine模式](https://segmentfault.com/a/1190000007111208) [關於range](https://my.oschina.net/u/2612999/blog/908114) [package 注意](https://zhuanlan.zhihu.com/p/36791917)