goroutine goroutine是Go語言中的輕量級線程實現,由Go運行時(runtime)管理。你將會發現,它的
使用出人意料得簡單。
假設我們需要實現一個函數Add(),它把兩個參數相加,並將結果列印到螢幕上,具體代碼
如下:
func Add(x, y int) {
z := x + y
fmt.Println(z)
}
那麼,如何讓這個函數並發執行呢?具體代碼如下:
go Add(1, 1)
是不是很簡單?
你應該已經猜到,“go”這個單詞是關鍵。與普通的函數調用相比,這也是唯一的區別。的
確, go是Go語言中最重要的關鍵字,這一點從Go語言本身的命名即可看出。
在一個函數調用前加上go關鍵字,這次調用就會在一個新的goroutine中並發執行。當被調用
的函數返回時,這個goroutine也自動結束了。需要注意的是,如果這個函數有傳回值,那麼這個
傳回值會被丟棄。
案例分析:
package mainimport ("fmt""time")func say(s string) {for i := 0; i < 5; i++ {//如果此處不加sleep則go say("world")將無列印輸出time.Sleep(100 * time.Millisecond)fmt.Println(s)}}func main() {go say("world")say("hello")}
Go 程式從初始化 main package 並執行 main() 函數開始,當 main() 函數返回時,程式退出,且程式並不等待其他 goroutine (非主 goroutine )結束。
channel
channel 是有類型的管道,可以用 channel 操作符 <- 對其發送或者接收值。
ch <- v // 將 v 送入 channel ch。v := <-ch // 從 ch 接收,並且賦值給 v。
(“箭頭”就是資料流的方向。)
和 map 與 slice 一樣,channel 使用前必須建立:
ch := make(chan int)
預設情況下,在另一端準備好之前,發送和接收都會阻塞。這使得 goroutine 可以在沒有明確的鎖或競態變數的情況下進行同步。 案例:
package mainimport "fmt"func sum(a []int, c chan int) {sum := 0for _, v := range a {sum += v}c <- sum // 將和送入 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 // 從 c 中擷取,後進先出,輸出結果17 -5 12fmt.Println(x, y, x+y)}
緩衝 channel
channel 可以是 帶緩衝的。為 make 提供第二個參數作為緩衝長度來初始化一個緩衝 channel:
ch := make(chan int, 100)
向帶緩衝的 channel 發送資料的時候,只有在緩衝區滿的時候才會阻塞。 而當緩衝區為空白的時候接收操作會阻塞。
package mainimport "fmt"func main() {ch := make(chan int, 2)ch <- 1ch <- 2//ch <- 3 //送入channel超出緩衝區,編譯時間會報錯all goroutines are asleep - deadlock!fmt.Println(<-ch)fmt.Println(<-ch)//fmt.Println(<-ch) //從channel取出值時超出緩衝區或緩衝區為空白都會報錯all goroutines are asleep - deadlock!}
range 和 close
寄件者可以 close 一個 channel 來表示再沒有值會被發送了。接收者可以通過指派陳述式的第二參數來測試 channel 是否被關閉:當沒有值可以接收並且 channel 已經被關閉,那麼經過
v, ok := <-ch
之後 ok 會被設定為 false。
迴圈 `for i := range c` 會不斷從 channel 接收值,直到它被關閉。
*注意:* 只有寄件者才能關閉 channel,而不是接收者。向一個已經關閉的 channel 發送資料會引起 panic。 *還要注意:* channel 與檔案不同;通常情況下無需關閉它們。只有在需要告訴接收者沒有更多的資料的時候才有必要進行關閉,例如中斷一個 range。
package mainimport ("fmt")func fibonacci(n int, c chan int) {x, y := 0, 1for i := 0; i < n; i++ {c <- xx, y = y, x+y}close(c) //此處如果不關閉channel,在下邊使用range遍曆到末尾時還會繼續取而導致超出緩衝區,報錯all goroutines are asleep - deadlock!}func main() {c := make(chan int, 10)go fibonacci(cap(c), c)for i := range c {fmt.Println(i)}}
select
select 語句使得一個 goroutine 在多個通訊操作上等待。
select 會阻塞,直到條件分支中的某個可以繼續執行,這時就會執行那個條件分支。當多個都準備好的時候,會隨機播放一個。 select {
case <-chan1:
// 如果 chan1 成功讀到資料,則進行該 case 處理語句
case chan2 <- 1:
// 如果成功向 chan2 寫入資料,則進行該 case 處理語句
default :
// 如果上面都沒有成功,則進入 default 處理流程
}
package mainimport "fmt"func fibonacci(c, quit chan int) {x, y := 0, 1for {select {case c <- x:x, y = y, x+ycase <-quit:fmt.Println("quit")return}}}func main() {c := make(chan int)quit := make(chan int)go func() {for i := 0; i < 10; i++ {fmt.Println(<-c)}quit <- 0}()fibonacci(c, quit)}
案例二
package mainimport ("fmt""time")func main() {tick := time.Tick(100 * time.Millisecond)boom := time.After(500 * time.Millisecond)for {select {case <-tick:fmt.Println("tick.")case <-boom:fmt.Println("BOOM!")returndefault:fmt.Println(" .")time.Sleep(50 * time.Millisecond)}}}