go語言之併發程式設計 channel
前面介紹了goroutine的用法,如果有多個goroutine的話相互之間是如何傳遞資料和通訊的呢。在C語言或者JAVA中,傳輸的方法包括共享記憶體,管道,訊號。而在Go語言中,有了更方便的方法,就是channel。在同一時刻,僅有一個goroutine能向一個通道傳送元素值,同時也僅有一個goroutine能從它那裡接收元素值。在通道中,各個元素值都是嚴格按照發送到此的先後順序排列的,最早被髮送到通道的元素會最新被接收。因此通道相當於一個FIFO的佇列。而且通道的元素也具有原子性,是不可被分割的。通道中的每一個元素值都只能被某一個goroutine接收,已被接收的的元素值會立刻從通道中刪除。
channel的型別表示法:採用chan關鍵字
type intchan chan int
var intchan chan int
上面兩種宣告表示一個chan int型別的變數。初始化後,變數intchan就可以用來傳遞int型別的元素值了。那麼如何從通道中傳送資料以及接收資料呢。Channel中採用<- 以及->符號
<- intchan就表示從intchan中傳送一個數據。intchan <- 就表示intchan接收一個數據
初始化通道
make(chan int,10)
這個表示式初始化了一個通道型別的值,傳遞給make函式的第一個引數表明,此值的具體型別是元素型別為int的通道型別。而第二個引數則指出該通道在同一時刻最多可以快取10個元素值。當然,也可以在初始化一個通道的時候省略第二引數值,比如make(chan int)。如果第二個引數被忽略了,表示被初始化的這個通道永遠無法快取任何元素值,傳送給它的元素值應該被立刻取走,否則傳送發的goroutine就會被暫停,直到有接收方接收這個元素值。第二個引數大於0的可以稱為緩衝通道,等於0的可以稱為非緩衝通道。
strChan:=make(chan string,3)
如果要從這個通道中接收值,那麼應該這樣寫程式碼 elem:=<-strChan。或者是elem,ok:=<-strChan。Ok是一個布林變數值,賦值成功為true,失敗為false
但是注意如果通道中沒有任何元素時,當前的goroutine會被阻塞在此。如果在進行接收操作之前或過程當中該通道被關閉了。那麼該操作會立即結束。並且變數elem會被賦予該通道的元素型別的零值。
來看一個使用channel的例子:程式碼如下
var strChan=make(chan string,3)
func main(){
synChan1:=make(chan struct{},1)
synChan2:=make(chan struct{},2)
go func(){
<-synChan1
fmt.Println("Received a sync signal and wait a second...[receiver]")
time.Sleep(time.Second)
for{
if elem,ok:=<-strChan;ok {
fmt.Println("Received:", elem, "[receiver]")
}else{
break
}
}
fmt.Println("Stopped.[receiver]")
synChan2 <- struct{}{}
}()
go func(){
for _,elem:=range[]string{"a","b","c","d"}{
strChan <- elem
fmt.Println("Sent:",elem,"[Sender]")
if elem == "c"{
synChan1 <- struct{}{}
fmt.Println("Sent a sync signal.[sender]")
}
}
fmt.Println("Wait for 2 seconds")
time.Sleep(time.Second*2)
close(strChan)
synChan2 <- struct{}{}
}()
<-synChan2
<-synChan2
}
執行結果如下:
Sent: a [Sender]
Sent: b [Sender]
Sent: c [Sender]
Sent a sync signal.[sender]
Received a sync signal and wait a second...[receiver]
Sent: d [Sender]
Wait for 2 seconds
Received: a [receiver]
Received: b [receiver]
Received: c [receiver]
Received: d [receiver]
Stopped.[receiver]
在這個例子中,先後啟用了兩個goroutine。分別用於演示在strChan之上的傳送和接收操作。
傳送操作的go函式:for迴圈中用於把切片的4個元素依次傳送給strChan。當傳送完第三個的時候,向synChan1傳送了一個訊號。這個訊號會使接收方接收到後恢復執行。當for迴圈結束後,讓當前的goroutine睡眠了2秒。這是為了等待接收方將4個值都接收完。再呼叫close關閉strChan通道。
接收操作的go函式:在synChan1收到訊號前一直等待,當收到訊號則恢復,說明strChan中已經有了3個元素。不過先睡眠1秒鐘再去讀。這是因為strChan的容量是3,所以傳送方在這1秒內傳送第4個值時會因strChan已滿而等待。直到接收方從strChan取出一個值。傳送方在關閉strChan後,接收方取不到正確的值,則退出迴圈
整個流程圖可以參考下圖
另外synChan2,這個channel的作用是為了不讓主goroutine過早結束執行。只有在接收方和傳送方都執行完了之後,synChan2才會有2個值在裡面。
當接收方從通道接收到一個值型別的值時,對該值的修改就不會影響到傳送發持有的那個值。但對於引用型別的值來說,這種修改會同時影響收發雙方持有的值。看下面的例子
var mapChan=make(chan map[string]int,1)
func main(){
synChan:=make(chan struct{},2)
go func(){
for{
if elem,ok := <-mapChan;ok{
elem["count"]++
}else{
break
}
}
fmt.Println("stopped.[receiver]")
synChan <- struct{}{}
}()
go func(){
countMap:=make(map[string]int)
for i:=0;i<5;i++{
mapChan <- countMap
time.Sleep(time.Millisecond)
fmt.Println("The count map:%v.[sender]\n",countMap)
}
close(mapChan)
synChan <- struct{}{}
}()
<-synChan
<-synChan
}
執行結果:
The count map:.[sender] map[count:1]
The count map:.[sender] map[count:2]
The count map:.[sender] map[count:3]
The count map:.[sender] map[count:4]
The count map:.[sender] map[count:5]
stopped.[receiver]