這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。Go使用channel和goroutine開發並行程式。
goroutinegoroutine是一個普通的函數,只是需要使用保留字go作為開頭。ready("Tea", 2) //普通函數調用go ready("Tea", 2) //ready() 作為goroutine運行
例:package mainimport ( "time" "fmt")func ready(w string, sec int){ time.Sleep(time.Duration(sec) * time.Second) fmt.Println(w, "is ready!")}func main(){ go ready("Tea", 2) go ready("Coffee", 1) fmt.Println("I'm waiting") time.Sleep(5 * time.Second)}輸出結果:I'm waiting //立刻輸出Coffee is ready! //1 秒後輸出Tea is ready! //2 秒後輸出
注意:上述例子如果刪除 time.Sleep(5 * time.Second) 程式輸出 I'm waiting後立即結束運行
例2:package main
import ( "fmt" "runtime")
func say(s string) { for i := 0; i < 5; i++ { runtime. () fmt.Println(s) }}
func main() { go say("world") //開一個新的Goroutines執行 say("hello") //當前Goroutines執行}輸出結果:helloworldhelloworldhelloworldhelloworldhello
runtime.Gosched()表示讓CPU把時間片讓給別人,下次某個時候繼續恢複執行該goroutine。預設情況下,調度器僅使用單線程,也就是說只實現了並發。想要發揮多核處理器的並行,需要在我們的程式中顯示的調用 runtime.GOMAXPROCS(n) 告訴調度器同時使用多個線程。GOMAXPROCS 設定了同時運行邏輯代碼的系統線程的最大數量,並返回之前的設定。如果n < 1,不會改變當前設定。以後Go的新版本中調度得到改進後,這將被移除。
channel必須使用make建立channel,如ci := make(chan int)cs := make(chan string)cf := make(chan interface{})建立channelci用於發送和接收整數,建立channelcs用於字串,以及channelcf使用了空介面來滿足各種類型。向channel發送或接收資料,是通過類似的操作符完成的:<-. 具體作用則依賴於操作符的位置:ci <- 1 //發送整數1 到channel ci<-ci //從channel ci 接收整數i := <-ci //從channel ci 接收整數,並儲存到i 中
例:package mainimport ( "time" "fmt")var c chan int //定義c作為int 型的channelfunc ready(w string, sec int){ time.Sleep(time.Duration(sec) * time.Second) fmt.Println(w, "is ready!") c <- 1 //發送整數1到channel c}func main(){ c = make(chan int) //初始化c go ready("Tea", 2) //用保留字go開始一個goroutine go ready("Coffee", 1) fmt.Println("I'm waiting") <-c //等待,直到從channel上接收一個值。注意,收到的值被丟棄了,如果想要儲存可以用 i := <-c <-c //兩個goroutines,接收兩個值}輸出結果:I'm waiting //立刻輸出Coffee is ready! //1 秒後輸出Tea is ready! //2 秒後輸出
上例中,不得不從channel中讀取兩次,如果不知道啟動了多少goroutine,可以使用select關鍵字。通過select(和其他東西)可以監聽channel上輸入的資料
例2:package mainimport "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)}輸出結果:17 -5 12
並行和並發:並發和並行的區別就是一個處理器同時處理多個任務和多個處理器或者是多核的處理器同時處理多個不同的任務。 前者是邏輯上的同時發生(simultaneous),而後者是物理上的同時發生. 並發性(concurrency),又稱共行性,是指能處理多個同時性活動的能力,並發事件之間不一定要同一時刻發生。 並行(parallelism)是指同時發生的兩個並發事件,具有並發的含義,而並發則不一定並行。 來個比喻:並發和並行的區別就是一個人同時吃三個饅頭和三個人同時吃三個饅頭
關閉channelx, ok = <-ch當ok被賦值為true意味著channel尚未被關閉,同時可以讀取資料。否則ok被賦值為false。在這個情況下表示channel被關閉。
Buffered Channels
Go也允許指定channel的緩衝大小,很簡單,就是channel可以儲存多少元素。ch:= make(chan bool, 4),建立了可以儲存4個元素的bool 型channel。在這個channel 中,前4個元素可以無阻塞的寫入。當寫入第5個元素時,代碼將會阻塞,直到其他goroutine從channel 中讀取一些元素,騰出空間。
例:package main
import "fmt"
func main() { c := make(chan int, 2)//修改2為1就報錯,修改2為3可以正常運行 c <- 1 c <- 2 fmt.Println(<-c) fmt.Println(<-c)}輸出結果:12
Range和Close
package main
import "fmt"func fibonacci(n int, c chan int) { x, y := 1, 1 for i := 0; i < n; i++ { c <- x x, y = y, x + y } close(c)}func main() { c := make(chan int, 10) go fibonacci(cap(c), c) for i := range c { fmt.Println(i) }}
輸出結果:11235813213455
for i := range c
能夠不斷的讀取channel裡面的資料,直到該channel被顯式的關閉。上面代碼我們看到可以顯式的關閉channel,生產者通過關鍵字close
函數關閉channel。關閉channel之後就無法再發送任何資料了,在消費方可以通過文法v, ok := <-ch
測試channel是否被關閉。如果ok返回false,那麼說明channel已經沒有任何資料並且已經被關閉。
記住應該在生產者的地方關閉channel,而不是消費的地方去關閉它,這樣容易引起panic
另外記住一點的就是channel不像檔案之類的,不需要經常去關閉,只有當你確實沒有任何發送資料了,或者你想顯式的結束range迴圈之類的
selectselect預設是阻塞的,只有當監聽的channel中有發送或接收可以進行時才會運行,當多個channel都準備好的時候,select是隨機的選擇一個執行的。
例:package main
import "fmt"
func fibonacci(c, quit chan int) { x, y := 1, 1 for { select { case c <- x: x, y = y, x + y case <-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)}輸出結果:11235813213455quit
在select
裡面還有default文法,select
其實就是類似switch的功能,default就是當監聽的channel都沒有準備好的時候,預設執行的(select不再阻塞等待channel)。
select {case i := <-c: // use idefault: // 當c阻塞的時候執行這裡}
逾時package mainimport "time"func main() { c := make(chan int) o := make(chan bool) go func() { for { select { case v := <- c: println(v) case <- time.After(5 * time.Second): println("timeout") o <- true break } } }() <- o}輸出結果:timeout
runtime goroutine
runtime包中有幾個處理goroutine的函數: Goexit
退出當前執行的goroutine,但是defer函數還會繼續調用
Gosched
讓出當前goroutine的執行許可權,調度器安排其他等待的任務運行,並在下次某個時候從該位置恢複執行
NumCPU
返回CPU核心數量
NumGoroutine
返回正在執行和排隊的任務總數
GOMAXPROCS
用來設定可以啟動並執行CPU核心數