- 取得連結
- X
- 以電子郵件傳送
- 其他應用程式
程式語言: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
可以利用 type 對基本類型增加 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
- }
- package 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
或者說,事實上 interface 並不是 addressable 的- 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)
- }
所以當呼叫 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
留言
張貼留言