Goroutine
Goroutine 是由 Go 語言本身管理的輕量級執行緒(User-space Thread),而不是由作業系統管理的執行緒(OS Thread),切換與創建成本極低。
背後使用 M:N 排程模型。這意味著,M 個 Goroutine 會被分配到 N 個作業系統執行緒上執行,當 Goroutine 阻塞時會調度繼續執行其他事情。
go 關鍵字用於創建 goroutine,一種 Fork-join 並行計算模型實踐把大任務切成獨立的小任務同時執行並整合起來。
func someFunc(num string) { fmt.Println(num)}
func main() { go someFunc("1") go someFunc("2") go someFunc("3")
// 避免 main goroutine 執行完畢關閉,故意等待 2 秒等待其他 goroutine 執行完畢 time.Sleep(time.Second * 2) fmt.Println("Hi")}可以發現每次打印數字的順序都可能不同,這就是 goroutine 背後並行執行代碼的證據。
channel
相較於其他語言共享記憶體並透過「替程式上鎖」來避免 Race Condition;Go 透過共享 Channel 確定任何給定時間只有一個 goroutine 可以存取資料。Channel 可以想像是一個 Queue:
func main() { myChannel := make(chan string)
go func() { myChannel <- "data" }()
// Blocking,只有關閉或接受訊息才會繼續 msg := <-myChannel fmt.Println(msg)}Unbuffered vs Buffered Channel
Channel 之間有送出與接收端,Unbuffered 意味著,只要送出 goroutine 就會一直等待直到接收:
ch := make(chan int)
go func() { fmt.Println("sending...") ch <- 1 // 阻塞到主 goroutine 接收 fmt.Println("sent")}()
fmt.Println("receiving...")value := <-ch // 進入此行,送端才會繼續fmt.Println("received:", value)而 Buffered 意味著送出並不一定馬上應對接收,而是送出訊息時 buffer 滿了才堵塞或是接收訊息空了才堵塞。
ch := make(chan int, 2)
ch <- 1 // 不阻塞ch <- 2 // 不阻塞// ch <- 3 會阻塞,因為 buffer 已滿
fmt.Println(<-ch) // 1fmt.Println(<-ch) // 2Select
如果多個 case 都可以執行,系統會進行統一偽隨機選擇來決定執行哪一個。
func main() { myChannel := make(chan string) myAnotherChannel := make(chan string)
go func() { myChannel <- "data" }()
go func() { myAnotherChannel <- "data2" }()
// Blocking,只有任一 case 關閉或接受訊息才會繼續 // 如果多個 case 都可以執行,系統會進行統一偽隨機選擇來決定執行哪一個。 select { case msg := <-myChannel: fmt.Println("a", msg) case msg2 := <-myAnotherChannel: fmt.Println("b", msg2) }
}延伸閱讀
- Master Go Programming With These Concurrency Patterns (in 40 minutes) - Kantan Coding
- Goroutines explained - Awesome
- Share Memory By Communicating - The Go Blog
- This is why Go Channels are awesome - Web Dev Cody