# 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 擴展

* 因此 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 切記首字母要大寫!!包括(
)**很重要!!!**
#### package import 原理
* 如果一個 `main` 導入其他的包,包會被順序導入
* 如果導入的包(pk1)依賴其他的包(pk2),會首先導入pk2然後初始化,pk2中的常數與變數,如果pk2中有 init 函數,會自動執行 init
* 所有包導入完成後,才會對 main 的常數和變數進行初始化,然後執行 main 中的 init 函數(如果有)最後執行 main 函數
* 如果一個包被導入多次,實際上只會導入一次

#### import 別名
```go=
import iii "fmt"
// 此处省略一些代码...
iii.Println("hello")
```
```go=
import . "fmt"
// 此处省略一些代码...
Println("hello")
:::info
有一個最特别的情況,如果别名是_的話,表示只註冊該包(初始化全局常數和變數,且執行其init函数),並不會實際引入該包。
:::
```
### 擴充已有的 Package
* 定義別名
* 使用合成(組合)


### Ducking type
* 介面(接口/interface)由使用者來定義
* 介面的實現是隱式讀
* 只要實現介面裡的方法
* 介面包括引用(T,type)&值(v,value) or


* 介面(接口)裡面有什麼
* 接口變數自帶指針
* 接口變數同樣採用值傳遞,幾乎不需要使用接口的指針
* 指針接收者實現只能以指針方式使用,值接收者則都可
* 將func接收者或回傳者改為`interface{}`就可接受任何類型的變數
* 強制轉型 `head.(int)` 拿出的值轉為 **int**
* 系統接口組合

### 函數式&閉包
:::info
```go=
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綁定到一個變數
```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

輸出不同的原因:不同作用域類型的變數初始化順序不同
上述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)`

## 相關範例學習
### 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)