- 取得連結
- 以電子郵件傳送
- 其他應用程式
程式語言:Go
The Little Go Book 官網
Go 官網
Go 規格
線上 Go 編譯工具
簡介:Go 的基本語法應用
初學 Golang 30 天(三)變數
[Golang] 相當好用但又要注意的defer
Golang- import 导入包的语法
How to use interfaces in Go Why value stored in an interface is not addressable in Golang
Go Data Structures: Interfaces
The Little Go Book 官網
Go 官網
Go 規格
線上 Go 編譯工具
簡介:Go 的基本語法應用
Terminal 基本指令 (Env Path 需有 C:\Go\bin)
go # 顯示可用指令 go build # 編譯執行檔於當前目錄 go clean # 將 build 產生的檔案都刪除 (install 的不會刪) go doc # 顯示 help 文件,例: go doc fmt # godoc -http=:6060 可到 http://localhost:6060 檢視文件。 go env # 顯示環境變數 # GOROOT: Go 的安裝位置 # GOPATH: Go 的工作目錄 go bug # 回報 bug go fix # 修正移植到新版本 GO 的錯誤 code go fmt # 排版代碼 go generate # 用於批量執行命令 go get # 安裝套件,可直接使用 github 網址 go install # 產生執行檔於 $GOPATH/bin,或是安裝 package 於 $GOPATH/pkg go list # 列出所有套件。例 go list ./... (... 表示在目錄下所有檔案與目錄) go run # 直接執行 Go code,go run -x main.go 可提供詳細資訊 go test # 自動讀取名為 *_test.go 的文件,並產生可運行測試的執行檔 go tool # 執行特定工具,例:fix or vet go version # 版本 go vet # 静態檢查,並回報可能的 bug
# 可使用瀏覽器連接 http://localhost:5000 或 http://主機IP:5000 查詢文件 godoc -http=:5000 # 可檢查 test 覆蓋率 go test -coverprofile=c.out go tool cover -html=c.out # 觀察性能的瓶頸,線上的時間表示以下所有區塊運行的時間 go test -bench . -cpuprofile cpu.out go tool pprof cpu.out web # 用瀏覽器觀看圖,需安裝 Graphviz # race 檢測 go run -race main.go # trace 程式碼 # 產生 trace file 三種方法 runtime/trace.Start net/http/pprof package go test -trace # 其中之一的用法 go test -trace trace.out pkg # 用瀏覽器觀看資料 go tool trace trace.out官方介紹一整套用於診斷 Go 程序中的邏輯和性能問題的 API 和工具
基本概念
- Go 是需要編譯、靜態型別、具有類似於 C 的語法與擁有 Garbage Collection 等特性的語言
- Go 語言裡,程式的進入點是在 main package 裡面的 main function
- import 關鍵字可以用來引用相關套件
package main import ( "fmt" ) func main() { fmt.Println("123") }
Import declaration Local name of Sin import "lib/math" math.Sin import m "lib/math" m.Sin import . "lib/math" Sin import _ "lib/math" only for init
- 如果 import 了套件, 卻沒有使用,會出現錯誤
- function 外的每個語法塊都必須以關鍵字開始( var 、 func 、等等)
- Go 沒有物件或繼承,也沒有許多物件導向的概念,比如說多形或重載
變數與宣告
- function 內如果宣告了變數,卻沒有使用,會出現錯誤
- 宣告變數若沒提供初始值,會有 default 值
- int: 0
- bool: false
- string: ""
- 各種宣告方法如下
package main var ag int = 3 // 可行,但很多餘,可拿掉 int,由值推論 var bg int // 宣告成 int var cg = 10 // 自行推論 type var dg, eg int // dg 跟 eg 都是 int // fg := 0 // 此方法無法宣告 global variable var ( // 記得要不同行,不然會錯 gg bool = false // 可行,但很多餘,可拿掉 bool,由值推論 hg int ig = "hello" jg = '字' // rune kg = `\n原始字串,可用在 regexp 可多行,但會自動去掉換行的\r 保證在所有平台上的值都是一樣的,例:Windows系統會把回車和換行一起放入文本文件中` ) func main() { println(ag, bg, cg, dg, eg, gg, hg, ig, jg, kg) var al int = 3 // 可行,但很多餘,可拿掉 int,由值推論 var bl int // 宣告成 int var cl = 10 // 自行推論 type var dl, el int // dl 跟 el 都是 int fl := 0 gl, hl, il := 0, true, "tacolin" // 這樣就可以不同型別寫在同一行 var ( // 記得要不同行,不然會錯 jl bool = false // 可行,但很多餘,可拿掉 bool,由值推論 kl int ll = "hello" ) println(al, bl, cl, dl, el, fl, gl, hl, il, jl, kl, ll) }
- 要記住 := 同時用於宣告變數以及為它賦值,而變數不能被宣告兩次
但這種情況是允許的,因為至少有一個新變數,但 power 的 type 仍無法被改變
而若是在不同的 scope 中,則會被視為重新宣告新變數
package main func main() { power := 1000 name, power := "Goku", 9000 println(name, power) if true { power := 2000 println(power) } println(power) }
- _ 空白識別符號
- 特別用在返回值不需要被實際指派的時候使用
可以不管返回的類型,重複的使用 _ 識別符號,無需事先宣告 - 基本類型
- bool
- string
- int, int8, int16, int32, int64
- uint uint8, uint16, uint32, uint64, uintptr
- byte // uint8 的別名
- rune // int32 的別名,也代表一個 Unicode 碼
- float32, float64, complex64, complex128
- const
- 宣告如下
package main //Pi 3.14 const Pi = 3.14 func main() { println("Happy", Pi, "Day") const World = "世界" println("Hello", World) const Truth bool = true println("Go rules?", Truth) }
- 數值常數是高精度的值
一個未指定類型的常數將由上下文來決定其類型package main func needInt(x int) int { return x/20 + 1 } func needFloat(x float64) float64 { return x * 0.1 } func main() { const ( Big = 1 << 100 Small = Big >> 99 ) println(needInt(Small)) println(needFloat(Small)) // overflow // println(needInt(Big)) println(needFloat(Big)) }
- pointer
- 宣告方法
package main func main() { a := 1 println(&a) change(&a) println(a) } func change(b *int) { *b++ }
function 宣告
- function 是一級型別 (first-class function)
- 可以用在任何地方 - 當作一個欄位型別、參數、回傳值
- 宣告方法
package main // 輸入一個變數,無回傳變數 func log(message string) { } // 輸入兩個變數,回傳一個變數 func add(a int, b int) int { return a + b } // 輸入兩個變數,可合併簡寫,回傳一個變數 func minus(a, b int) int { return a - b } // 輸入一個變數,回傳兩個變數 func power(name string) (int, bool) { return 1, true } // 無輸入變數,回傳兩個變數,可事先宣告 func positin() (x, y int) { x = 1 y = 2 return } func main() { // _ 空白識別符號是特別用在返回值不需要被實際指派的時候使用 // 可以不管返回的類型,重複的使用 _ 識別符號 // 但無需宣告其 type _ = add(1, 2) power, _ := power("goku") println(power) }
- function 內不能再有 function,除非綁定在變數上,也就是 closure
package main import "fmt" func adder() func(int) int { sum := 0 f := func(x int) int { sum += x return sum } return f } func main() { pos, neg := adder(), adder() for i := 0; i < 10; i++ { fmt.Println(pos(i), neg(-2*i)) } }
- function 傳遞參數方式
- 除了 Map, slices
- 其餘皆是 copy 方式,甚至是 array,若希望更改值,請用 pointer
- 但使用 slice 的 append 時,若 cap 不夠時,會被重新分配新的底層 array
control
- for
package main func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } println(sum) sum = 1 // for ; sum < 1000; { 前後可為空,但 go fmt 後會被移除,類似 while for sum < 1000 { sum += sum } println(sum) sum = 1 // 無窮迴圈 for { sum += 1 if sum == 100 { break } } println(sum) scores := [4]int{9001, 9333, 212, 33} // for in array for index, value := range scores { println(index, value) } // count n := 0 for range "Hello, 世界" { n++ } println(n) }
- if
package main func main() { i := 0 if i == 0 { println(i) } // 初始化 i,此時的 i 作用域僅在 if else 範圍之內 if i := 2; i == 2 { println(i) // 2 } println(i) // 0 if i > 0 { } else { println(i) } }
- switch
package main func main() { i := 2 switch i { case 1: // 執行下一個 fallthrough case 2: println("two") case 3, 4: // 3 or 4 println("three, four") default: println("number") } switch { case i == 1 || i == 2: println("one or two") case i == 4: println("four") case i > 5 && i < 7: println("six") default: println("number") } }
struct
- 宣告方法
package main import ( "fmt" ) // Saiyan struct type Saiyan struct { Name string Power int } // 事先宣告值 var a = Saiyan{ Name: "a", Power: 9000, } func main() { // 後來指定值 b := Saiyan{} b.Name = "b" b.Power = 1000 // 依順序指定 c := Saiyan{"c", 8000} // default 值 d := Saiyan{} // 指標宣告 e := new(Saiyan) // 結果同上 f := &Saiyan{} fmt.Println(a, b, c, d, e, f) }
- method
package main import "fmt" type Saiyan struct { Name string Power int } func (s *Saiyan) Super() { s.Power += 10000 } func (s Saiyan) Lower() { s.Power -= 100 } func main() { a := Saiyan{"a", 9001} a.Super() a.Lower() // 不會改變值 // 結果同上 b := &Saiyan{"b", 9001} b.Super() b.Lower() // 不會改變值 fmt.Println(a.Power, b.Power) // 將列印出 19001 }
可以利用 type 對基本類型增加 methodpackage main import ( "fmt" "math" ) type MyFloat float64 func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } func main() { f := MyFloat(-math.Sqrt2) fmt.Println(f.Abs()) }
- 包含同類型
package main import ( "fmt" ) // Saiyan struct type Saiyan struct { Name string Power int Father *Saiyan //include 自己必須用指標才可以 } func main() { a := &Saiyan{ Name: "Gohan", Power: 1000, Father: &Saiyan{ Name: "Goku", Power: 9001, Father: nil, }, } fmt.Println(a) }
- Composition
- 因為沒有提供明確的欄位名稱,所以可以透過組合隱性的存取這個欄位和 method
但 Go 的編譯器仍會給他一個欄位名稱,但在上層若有同樣的屬性,例:Name,會以上層為主
package main import ( "fmt" ) type Person struct { Name string } func (p *Person) Introduce() { fmt.Printf("Hi, I'm %s\n", p.Name) } type Saiyan struct { *Person //Name string // 會覆寫掉 Person 的 Name Power int } func main() { a := &Saiyan{ Person: &Person{"a"}, Power: 9001, } a.Introduce() fmt.Println(a.Name) fmt.Println(a.Person.Name) }
- 可視性
- 可被外部引用
- 宣告類型或 method 時以大寫開頭
- 不可被外部引用,同一個套件中的程式碼才能夠存取這些欄位
- 宣告類型或 method 時以小寫開頭
Map、Array、Slice
- Array
- 宣告後即固定大小
- 宣告方法
package main func main() { var a [10]int a[0] = 339 println(len(a)) // 10 b := [4]int{9001, 9333, 212, 33} for index, value := range b { println(index, value) } }
- Slice
- 宣告後仍可變動大小
- 類似 python 的 list
- 宣告方法
package main import "fmt" func main() { // 已有初始值 a := []string{"leto", "jessica", "paul"} fmt.Println(a) // 宣告長度是 10 ,容量是 10 的 slice b := make([]bool, 10) fmt.Println(b) // 宣告長度是 0 ,容量是 0 的 slice,等同 nil,只能用 append 新增 var c []string fmt.Println(c) // 宣告長度是 0 ,容量是 20 的 slice d := make([]int, 0, 20) fmt.Println(d) }
- 賦值方法
package main import "fmt" func main() { // 建立一個長度是 0 ,容量是 10 的 slice a := make([]int, 0, 10) // c[7] = 9033 無法這麼做,因長度為 0 a = append(a, 5) fmt.Println(len(a), a) // 1 [5] a = a[0:8] // 為了將值放在 c[7],需先擴展,前提是容量夠 a[7] = 9033 fmt.Println(a) // [5 0 0 0 0 0 0 9033] b := make([]int, 5) b = append(b, 9332) // 因長度已有 5,所以是 append 到第六個 fmt.Println(b) // [0 0 0 0 0 9332] }
- 每次擴展的大小
package main import "fmt" func main() { scores := make([]int, 0, 5) c := cap(scores) fmt.Println(c) for i := 0; i < 25; i++ { scores = append(scores, i) // 如果容量改變了 // Go 為了容納新的資料,會增加陣列的長度 // window64 是每次兩倍,所以是 5, 10, 20, 40 // window32 不見得是每次兩倍,而是 5, 12, 24, 48 if cap(scores) != c { c = cap(scores) fmt.Println(c) } } }
- 值會被改變,可用 copy 預防
package main import "fmt" func main() { scores := []int{1, 2, 3, 4, 5} slice := scores[2:4] slice[0] = 999 fmt.Println(scores) // [1 2 999 4 5] }
- Go 不支援負索引值
- Map
- 類似 python 的 dictionary
- 宣告方法
package main import ( "fmt" ) func main() { a := make(map[string]int) a["goku"] = 9001 fmt.Println(len(a), a) // 1 map[goku:9001] //利用雙賦值,檢查是否存在 power, exists := a["vegeta"] fmt.Println(power, exists) // 0 false // 刪除,沒有任何的回傳,可以呼叫一個不存在的 key delete(a, "goku") delete(a, "noexist") fmt.Println(len(a), a) // 0 map[] // 預先定義初始大小,有助於提升效能 b := make(map[string]int, 100) fmt.Println(len(b), b) // 0 map[] c := map[string]int{ "goku": 9001, "gohan": 2044, } // 無順序性 for key, value := range c { fmt.Println(key, value) } // nil,此時無法賦值,需先用 make var e map[string]int fmt.Println(e == nil) // true e = make(map[string]int) }
-
無法直接對 map 中的 struct 做出修改
因 map 中的 value 是不可尋址的
原本 struct 的資料可能在地址A,但當 map 擴展時地址A就不是原來的資料,所以go不允许寫入數據
兩種解法:用 struct pointer or 重新賦值
但在 Go2 可能會可行package main import ( "fmt" ) type s struct { a int } func main() { m1 := make(map[string]s) m1["k"] = s{a: 5} fmt.Printf("%v\n", m1) //重新賦值 tmp := m1["k"] tmp.a = 8 m1["k"] = tmp fmt.Printf("%v\n", m1) //cannot assign to struct field m["k"].a in map //m["k"].a = 8 //struct pointer m2 := make(map[string]*s) m2["k"] = &s{a: 5} fmt.Printf("%v\n", m2["k"]) m2["k"].a = 8 fmt.Printf("%v\n", m2["k"]) }
package
- 位置:$GOPATH/src
- package 與 folder 建議同名,可以不同名,但一個資料夾只能有一個 pakcage(不包含 sub-folder)
import 使用的是路徑,而使用時需用宣告的 package 名字import xxx/xxxx/folderName packageName.function()
- 檔案不見得需同名,而且可以多個
- 範例
- 架構
$GOPATH/src - shopping pricecheck.go - db models.go func.go - main main.go
- 程式碼
- main.go
package main import ( "fmt" "shopping" ) func main() { fmt.Println(shopping.PriceCheck(4343)) }
- pricecheck.go
package shopping import ( "shopping/db" ) func PriceCheck(itemId int) (float64, bool) { item := db.LoadItem(itemId) if item == nil { return 0, false } return item.Price, true }
- models.go
package db type Item struct { Price float64 }
- func.go
package db func LoadItem(id int) *Item { return &Item{ Price: 9.001, } }
- 避免循環引用,可使用 interface
- 共享套件的重要規則就是,不應該從會引用自己的套件引用任何東西
例:不該引用 shopping 套件或其他子套件中的任何東西 - 可視性
- 可被外部引用
- 宣告類型或 function 時以大寫開頭
- 不可被外部引用,同一個套件中的程式碼才能夠存取這些欄位
- 宣告類型或 function 時以小寫開頭
interface
- 用於多種變化的接口
- 宣告方法
package main import ( "fmt" ) func whatType(i interface{}) { // 運用 switch 決定做什麼事 switch i.(type) { case int: fmt.Println("int") case bool: fmt.Println("bool") default: fmt.Println("ohters") } } func add(a interface{}, b interface{}) interface{} { // 若已知 type,可直接轉態 return a.(int) + b.(int) } func main() { a, b := 1, 3 whatType(a) fmt.Println(add(a, b)) }
package main import "fmt" type Logger interface { Log(message string) } type SqlLogger struct{} func (SqlLogger) Log(m string) { fmt.Println("SqlLogger", m) } type ConsoleLogger struct{} func (ConsoleLogger) Log(m string) { fmt.Println("ConsoleLogger", m) } type FileLogger struct{} func (FileLogger) Log(m string) { fmt.Println("FileLogger", m) } type Server struct { logger Logger } func process(logger Logger) { logger.Log("hello!") } func main() { a := Server{ logger: SqlLogger{}, } a.logger.Log("123") process(a.logger) b := Server{ logger: ConsoleLogger{}, } b.logger.Log("123") process(b.logger) c := Server{ logger: FileLogger{}, } c.logger.Log("123") process(c.logger) }
- 在 Go 中,interface 的連結是隱性的
- 如範例,如果結構有一個 function Log,且參數是 string,並且沒有回傳值,便會當作是一個 Logger
- struct 的型態會影響 interface 的判斷
The Go Playground
package main type Interface interface { method() } type S1 struct{} func (s S1) method() {} type S2 struct{} func (s *S2) method() {} type S3 struct{} func (s S3) methodV() {} func (s *S3) methodP() {} func main() { s1 := S1{} _ = Interface(s1) s1p := &S1{} _ = Interface(s1p) s2 := S2{} _ = Interface(s2) // S2 does not implement Interface (method method has pointer receiver) s2p := &S2{} _ = Interface(s2p) // 但若只是單純的呼叫 method,Go 將會自動轉換 // 因 Go 可以單純將變數依其型參轉換,進而當作參數傳入 method // 在這裡並不需要判斷是什麼類型,擁有什麼樣的 method s3 := S3{} s3.methodV() s3.methodP() s3p := &S3{} s3p.methodV() s3p.methodP() // 因直接呼叫 S3{} 是不可尋址,它只是個暫時性的位置 // 所以無法呼叫對應的 pointer method S3{}.methodV() S3{}.methodP() // cannot call pointer method on S3 literal (&S3{}).methodV() (&S3{}).methodP() }
原因在於
指向 value 的 pointer 可以是很多種類型,所以無法反推
但 pointer 指向的 value 卻是唯一的,所以可以確定
The Go Playground
package main import ( "fmt" ) type Interface interface { test() } type Interface2 interface { test() test2() } type S1 struct{} func (s *S1) test() {} func (s *S1) test2() {} func main() { s1p := &S1{} fmt.Printf("%T\n", s1p) //*main.S1 s1i := Interface(&S1{}) fmt.Printf("%T\n", s1i) //*main.S1 // 因指向值的 pointer 可能為複數類型,所以 Go 無法反推其 pointer method // 如下 // 究竟指向 s 的 pointer 是 Interface 還是 S1 呢? // 這決定是否合乎 Interface2 的接口,因 Interface 並無 test2() s := S1{} Interface2(s) // S1 does not implement Interface2 (test method has pointer receiver) }
或者說,事實上 interface 並不是 addressable 的
所以當呼叫 pointer 的 method 是會出錯的
因為無法傳地址過去給 method
試想以下情況
建立一個 A 並把它存在 I
type I interface{} type A int type B string func main() { var a A = 5 var i I = a fmt.Printf("i is of type %T\n", i) var aPtr *A aPtr = &(i.(A)) // 這是不被允許的語法,但在此假設如果能拿到 address // 然後建立一個 B 放在 i 中 var b B = "hello" i = b fmt.Printf("i is of type %T, aPtr is of type %T\n", i, aPtr) // 理論上 output // i is of type main.A // i is of type main.B, aPtr is of type *main.A // 但在此出現矛盾了,aPtr 的 type 是 *main.A,但它卻指向了 main.B ?? // interface 是動態決定類型,所以 interface 是不能 addressable 的 }
Error
- Go 主要是透過返回值來處理錯誤,並沒有一般語言的異常處理
- Go 有 panic 和 recover function,不過它們很少使用
panic 類似於拋出異常,recover 類似於 catch - 內建 error 格式
type error interface { Error() string }
- 使用方法
package main import ( "errors" "fmt" "time" ) func process(count int) error { if count < 1 { // 使用內建的 error return errors.New("Invalid count") } return nil } // 自定錯誤,只要符合內建的 error 格式 type MyError struct { When time.Time What string } func (e *MyError) Error() string { return fmt.Sprintf("at %v, %s", e.When, e.What) } func run() error { return &MyError{ time.Now(), "it didn't work", } } func main() { fmt.Println(process(0)) // 內建錯誤 fmt.Println(run()) // 自定錯誤 }
Defer
- function 返回前執行
- 呼叫與執行 defer 採取的是 LIFO,function 中最後一個 defer 將優先執行
- 變數的傳遞,會在呼叫 defer 的時候便直接傳入,後續變數有更正也不會修改
- 使用方法,可用在讀檔後的關閉檔案,以免忘記
package main import ( "fmt" "os" ) func main() { file, err := os.Open("a_file_to_read") if err != nil { fmt.Println(err) return } defer file.Close() // 讀檔 }
package main import ( "errors" "fmt" ) func useAClosure() error { var err error defer func() { fmt.Printf("useAClosure: err has value %v\n", err) }() err = errors.New("Error 1") fmt.Println("Finish func useAClosure") return err } func deferPrintf() error { var err error // 呼叫的時候會把參數帶過去,所以 err 是 nil defer fmt.Printf("deferPrintf: err has value %v\n", err) err = errors.New("Error 2") // 注意這裡是 LIFO,所以呼叫會是 43210 for i := 0; i < 5; i++ { defer fmt.Printf("%d ", i) } fmt.Println("Finish func deferPrintf") return err } func main() { useAClosure() deferPrintf() }
Concurrency (並行)
- Goroutine
- goroutine 有點類似於執行緒,但它是由 Go 自己來調度安排的,而不是由作業系統
- 主程式不會等到所有 goroutine 執行完才結束
- 使用方法
package main import ( "fmt" "time" ) func main() { fmt.Println("start") go process() go process() go process() time.Sleep(time.Millisecond * 10) // 這是不好的方式,請別這麼做! fmt.Println("done") } func process() { fmt.Println("processing") }
- 同步
- 在並行化的程式中,唯一安全的方式是讀取變數
- 可以有很多程式去讀一個變數,但寫入變數必須是同步的
- 別因為加了鎖,反而從開在一個十線道的,突然轉往一個單行道
- 小心 dead lock
例如,當 goroutineA 有鎖 A,但它想要存取鎖 B,而 goroutineB 擁有鎖 B,但它想要存取鎖 A,結果互相鎖住了 - 簡單 lock 範例,但不建議,因只是個單行道,並無改進效率
package main import ( "fmt" "sync" "time" ) var ( counter = 0 lock sync.Mutex ) func main() { for i := 0; i < 20; i++ { go incr() } time.Sleep(time.Millisecond * 10) // 不夠精準,不好的寫法 } func incr() { lock.Lock() defer lock.Unlock() counter++ fmt.Println(counter) }
- Channel
- 對於簡單的例子,應使用 sync.Mutex 和 sync.RWMutex
- 一個 Channel 就是不同的 goroutine 之間用來傳遞資料溝通的管道
- 默認情況下,在另一端準備好之前,發送和接收都會阻塞,除非使用 select
- 傳送
CHANNEL <- DATA
- 接收
VAR := <-CHANNEL
- 關閉
close(CHANNEL)
- 使用方法
package main import "fmt" func sum(a []int, c chan int) { sum := 0 for _, v := range a { sum += v } c <- sum // send sum to c } func main() { a := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go sum(a[:len(a)/2], c) go sum(a[len(a)/2:], c) x, y := <-c, <-c // receive from c fmt.Println(x, y, x+y) }
- 可以指定只能傳送 或 只能接收,宣告時加上箭頭
package main import ( "fmt" "time" ) func send(c chan<- int) { c <- 1 } func receive(c <-chan int) { fmt.Println(<-c) } func main() { c := make(chan int) go send(c) go receive(c) time.Sleep(time.Millisecond * 50) }
- buffer
- 改為 buffer 緩衝,不至於一開始就阻塞
package main import ( "fmt" "math/rand" "time" ) func main() { // 建立暫存 c := make(chan int, 100) for i := 0; i < 5; i++ { worker := &Worker{id: i} go worker.process(c) } for { c <- rand.Int() // 因來不及處理,即使有 buffer,還是會達到上限 fmt.Println(len(c)) time.Sleep(time.Millisecond * 50) } } type Worker struct { id int } func (w *Worker) process(c chan int) { for { data := <-c fmt.Printf("worker %d got %d\n", w.id, data) // 讓程式來不及處理傳送來的數據 time.Sleep(time.Millisecond * 5000) } }
- Close
- 利用 close 終止 goroutine
package main import ( "fmt" "runtime/debug" ) func main() { jobs := make(chan int, 5) done := make(chan bool) go func() { for { // channel 已被 close 且所有值都已被接收,則 more 變為 false j, more := <-jobs if more { fmt.Println("received job", j) } else { fmt.Println("received all jobs") done <- true return } } }() for j := 1; j <= 3; j++ { fmt.Println("sent job", j) jobs <- j } // close channel 不再傳送 close(jobs) fmt.Println("sent all jobs") <-done // 偵測是否有未關閉的 goroutine debug.SetTraceback("all") panic(1) }
- 搭配 Select (丟棄資料)
- 無 channel 可用時
- 無 default,持續等待直到有一個可用的 channel
- 有 default ,直接執行 default 下的程式碼
- 多個 channel 可用時,select 會隨機選擇一個 channel
- 使用方法
package main import ( "fmt" "time" ) func after(d time.Duration) chan bool { c := make(chan bool) go func() { time.Sleep(d) c <- true }() return c } func main() { c := make(chan int) for i := 0; i < 5; i++ { worker := &Worker{id: i} go worker.process(c) } i := 0 for { select { case c <- i: case <-after(time.Millisecond * 100): // 可用 time.After(time.Millisecond * 100) 取代 // 逾時丟掉資料 fmt.Println("timed out,drop out", i) } time.Sleep(time.Millisecond * 50) i++ } } type Worker struct { id int } func (w *Worker) process(c chan int) { for { data := <-c fmt.Printf("worker %d got %d\n", w.id, data) // 讓程式來不及處理傳送來的數據 time.Sleep(time.Millisecond * 5000) } }
參考
1.3 Go 命令初學 Golang 30 天(三)變數
[Golang] 相當好用但又要注意的defer
Golang- import 导入包的语法
How to use interfaces in Go Why value stored in an interface is not addressable in Golang
Go Data Structures: Interfaces
留言
張貼留言