1. 程式人生 > >go語言之併發程式設計 channel

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]