Goroutines和Channels(四)

若是說goroutine是Go語言程序的併發體的話,那麼channels則是它們之間的通訊機制。express

一個channel是一個通訊機制,它可讓一個goroutine經過它給另外一個goroutine發送值信息。編程

每一個channel都有一個特殊的類型,也就是channels可發送數據的類型。一個能夠發送int類型數據的channel通常寫爲chan int。緩存

 

使用內置的make函數,咱們能夠建立一個channel:網絡

ch := make(chan int) // ch has type 'chan int'

  

和map相似,channel也對應一個make建立的底層數據結構的引用。數據結構

當咱們複製一個channel或用於函數參數傳遞時,咱們只是拷貝了一個channel引用,所以調用者和被調用者將引用同一個channel對象。併發

和其它的引用類型同樣,channel的零值也是nil。app

 

兩個相同類型的channel可使用==運算符比較。若是兩個channel引用的是相同的對象,那麼比較的結果爲真。一個channel也能夠和nil進行比較。tcp

一個channel有發送和接受兩個主要操做,都是通訊行爲。函數

 

一個發送語句將一個值從一個goroutine經過channel發送到另外一個執行接收操做的goroutine。線程

發送和接收兩個操做都使用<-運算符。

在發送語句中,<-運算符分割channel和要發送的值。

在接收語句中,<-運算符寫在channel對象以前。一個不使用接收結果的接收操做也是合法的。

ch <- x  // a send statement
x = <-ch // a receive expression in an assignment statement
<-ch     // a receive statement; result is discarded

  

Channel還支持close操做,用於關閉channel,隨後對基於該channel的任何發送操做都將致使panic異常。

對一個已經被close過的channel進行接收操做依然能夠接受到以前已經成功發送的數據;若是channel中已經沒有數據的話將產生一個零值的數據。

使用內置的close函數就能夠關閉一個channel:

close(ch)

  

以最簡單方式調用make函數建立的是一個無緩存的channel。

可是咱們也能夠指定第二個整型參數,對應channel的容量。若是channel的容量大於零,那麼該channel就是帶緩存的channel。

 

ch = make(chan int)    // unbuffered channel
ch = make(chan int, 0) // unbuffered channel
ch = make(chan int, 3) // buffered channel with capacity 3

  

一個基於無緩存Channels的發送操做將致使發送者goroutine阻塞,直到另外一個goroutine在相同的Channels上執行接收操做,當發送的值經過Channels成功傳輸以後,兩個goroutine能夠繼續執行後面的語句。反之,若是接收操做先發生,那麼接收者goroutine也將阻塞,直到有另外一個goroutine在相同的Channels上執行發送操做。

 

基於無緩存Channels的發送和接收操做將致使兩個goroutine作一次同步操做。由於這個緣由,無緩存Channels有時候也被稱爲同步Channels。當經過一個無緩存Channels發送數據時,接收者收到數據發生在喚醒發送者goroutine以前(譯註:happens before,這是Go語言併發內存模型的一個關鍵術語!)。

 

在討論併發編程時,當咱們說x事件在y事件以前發生(happens before),咱們並非說x事件在時間上比y時間更早;咱們要表達的意思是要保證在此以前的事件都已經完成了,例如在此以前的更新某些變量的操做已經完成,你能夠放心依賴這些已完成的事件了。

 

當咱們說x事件既不是在y事件以前發生也不是在y事件以後發生,咱們就說x事件和y事件是併發的。這並非意味着x事件和y事件就必定是同時發生的,咱們只是不能肯定這兩個事件發生的前後順序。在下一章中咱們將看到,當兩個goroutine併發訪問了相同的變量時,咱們有必要保證某些事件的執行順序,以免出現某些併發問題。(意思就是不肯定線程執行順序)

 

func main() {
    conn, err := net.Dial("tcp", "localhost:8000")
    if err != nil {
        log.Fatal(err)
    }
    done := make(chan struct{})
    go func() {
        io.Copy(os.Stdout, conn) // NOTE: ignoring errors
        log.Println("done")
        done <- struct{}{} // signal the main goroutine
    }()
    mustCopy(conn, os.Stdin)
    conn.Close()
    <-done // wait for background goroutine to finish
}

  

當用戶關閉了標準輸入,主goroutine中的mustCopy函數調用將返回,而後調用conn.Close()關閉讀和寫方向的網絡鏈接。

關閉網絡鏈接中的寫方向的鏈接將致使server程序收到一個文件(end-of-file)結束的信號。

關閉網絡鏈接中讀方向的鏈接將致使後臺goroutine的io.Copy函數調用返回一個「read from closed connection」(「從關閉的鏈接讀」)相似的錯誤,所以咱們臨時移除了錯誤日誌語句;(須要注意的是go語句調用了一個函數字面量,這Go語言中啓動goroutine經常使用的形式。)

在後臺goroutine返回以前,它先打印一個日誌信息,而後向done對應的channel發送一個值。主goroutine在退出前先等待從done對應的channel接收一個值。所以,老是能夠在程序退出前正確輸出「done」消息。

基於channels發送消息有兩個重要方面。首先每一個消息都有一個值,可是有時候通信的事實和發生的時刻也一樣重要。當咱們更但願強調通信發生的時刻時,咱們將它稱爲消息事件。有些消息事件並不攜帶額外的信息,它僅僅是用做兩個goroutine之間的同步,這時候咱們能夠用struct{}空結構體做爲channels元素的類型,雖然也可使用bool或int類型實現一樣的功能,done <- 1語句也比done <- struct{}{}更短。

相關文章
相關標籤/搜索