1. 程式人生 > >Goroutines和Channels(四)

Goroutines和Channels(四)

IT capacity 額外 有一個 連接 -o AC 簡單 內存

如果說goroutine是Go語言程序的並發體的話,那麽channels則是它們之間的通信機制。

一個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。

兩個相同類型的channel可以使用==運算符比較。如果兩個channel引用的是相同的對象,那麽比較的結果為真。一個channel也可以和nil進行比較。

一個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{}{}更短。

Goroutines和Channels(四)