C語言中文網 目錄
首頁 > Go語言教程 > Go語言并發 閱讀:8,048

Go語言通道(chan)——goroutine之間通信的管道

單純地將函數并發執行是沒有意義的。函數與函數間需要交換數據才能體現并發執行函數的意義。雖然可以使用共享內存進行數據交換,但是共享內存在不同的 goroutine 中容易發生競態問題。為了保證數據交換的正確性,必須使用互斥量對內存進行加鎖,這種做法勢必造成性能問題。

Go 語言提倡使用通信的方法代替共享內存,這里通信的方法就是使用通道(channel),如下圖所示。


圖:goroutine 與 channel 的通信

在地鐵站、食堂、洗手間等公共場所人很多的情況下,大家養成了排隊的習慣,目的也是避免擁擠、插隊導致的低效的資源使用和交換過程。代碼與數據也是如此,多個 goroutine 為了爭搶數據,勢必造成執行的低效率,使用隊列的方式是最高效的,channel 就是一種隊列一樣的結構。

通道的特性

Go 語言中的通道(channel)是一種特殊的類型。在任何時候,同時只能有一個 goroutine 訪問通道進行發送和獲取數據。goroutine 間通過通道就可以通信。

通道像一個傳送帶或者隊列,總是遵循先入先出(First In First Out)的規則,保證收發數據的順序。

聲明通道類型

通道本身需要一個類型進行修飾,就像切片類型需要標識元素類型。通道的元素類型就是在其內部傳輸的數據類型,聲明如下:

var 通道變量 chan 通道類型

  • 通道類型:通道內的數據類型。
  • 通道變量:保存通道的變量。

chan 類型的空值是 nil,聲明后需要配合 make 后才能使用。

創建通道

通道是引用類型,需要使用 make 進行創建,格式如下:

通道實例 := make(chan 數據類型)

  • 數據類型:通道內傳輸的元素類型。
  • 通道實例:通過make創建的通道句柄。

請看下面的例子:
ch1 := make(chan int)                 // 創建一個整型類型的通道
ch2 := make(chan interface{})         // 創建一個空接口類型的通道, 可以存放任意格式

type Equip struct{ /* 一些字段 */ }
ch2 := make(chan *Equip)             // 創建Equip指針類型的通道, 可以存放*Equip

使用通道發送數據

通道創建后,就可以使用通道進行發送和接收操作。

1) 通道發送數據的格式

通道的發送使用特殊的操作符<-,將數據通過通道發送的格式為:

通道變量 <- 值

  • 通道變量:通過make創建好的通道實例。
  • 值:可以是變量、常量、表達式或者函數返回值等。值的類型必須與ch通道的元素類型一致。

2) 通過通道發送數據的例子

使用 make 創建一個通道后,就可以使用<-向通道發送數據,代碼如下:
// 創建一個空接口通道
ch := make(chan interface{})
// 將0放入通道中
ch <- 0
// 將hello字符串放入通道中
ch <- "hello"

3) 發送將持續阻塞直到數據被接收

把數據往通道中發送時,如果接收方一直都沒有接收,那么發送操作將持續阻塞。Go 程序運行時能智能地發現一些永遠無法發送成功的語句并做出提示,代碼如下:
package main

func main() {
    // 創建一個整型通道
    ch := make(chan int)

    // 嘗試將0通過通道發送
    ch <- 0
}
運行代碼,報錯:

fatal error: all goroutines are asleep - deadlock!

報錯的意思是:運行時發現所有的 goroutine(包括main)都處于等待 goroutine。也就是說所有 goroutine 中的 channel 并沒有形成發送和接收對應的代碼。

使用通道接收數據

通道接收同樣使用<-操作符,通道接收有如下特性:
① 通道的收發操作在不同的兩個 goroutine 間進行。

由于通道的數據在沒有接收方處理時,數據發送方會持續阻塞,因此通道的接收必定在另外一個 goroutine 中進行。

② 接收將持續阻塞直到發送方發送數據。

如果接收方接收時,通道中沒有發送方發送數據,接收方也會發生阻塞,直到發送方發送數據為止。

③ 每次接收一個元素。
通道一次只能接收一個數據元素。

通道的數據接收一共有以下 4 種寫法。

1) 阻塞接收數據

阻塞模式接收數據時,將接收變量作為<-操作符的左值,格式如下:

data := <-ch

執行該語句時將會阻塞,直到接收到數據并賦值給 data 變量。

2) 非阻塞接收數據

使用非阻塞方式從通道接收數據時,語句不會發生阻塞,格式如下:

data, ok := <-ch

  • data:表示接收到的數據。未接收到數據時,data 為通道類型的零值。
  • ok:表示是否接收到數據。

非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要實現接收超時檢測,可以配合 select 和計時器 channel 進行,可以參見后面的內容。

3) 接收任意數據,忽略接收的數據

阻塞接收數據后,忽略從通道返回的數據,格式如下:

<-ch

執行該語句時將會發生阻塞,直到接收到數據,但接收到的數據會被忽略。這個方式實際上只是通過通道在 goroutine 間阻塞收發實現并發同步。

使用通道做并發同步的寫法,可以參考下面的例子:
package main

import (
    "fmt"
)

func main() {

    // 構建一個通道
    ch := make(chan int)

    // 開啟一個并發匿名函數
    go func() {

        fmt.Println("start goroutine")

        // 通過通道通知main的goroutine
        ch <- 0

        fmt.Println("exit goroutine")

    }()

    fmt.Println("wait goroutine")

    // 等待匿名goroutine
    <-ch

    fmt.Println("all done")

}
執行代碼,輸出如下:
wait goroutine
start goroutine
exit goroutine
all done

代碼說明如下:
  • 第 10 行,構建一個同步用的通道。
  • 第 13 行,開啟一個匿名函數的并發。
  • 第 18 行,匿名 goroutine 即將結束時,通過通道通知 main 的 goroutine,這一句會一直阻塞直到 main 的 goroutine 接收為止。
  • 第 27 行,開啟 goroutine 后,馬上通過通道等待匿名 goroutine 結束。

4) 循環接收

通道的數據接收可以借用 for range 語句進行多個元素的接收操作,格式如下:
for data := range ch {

}
通道 ch 是可以進行遍歷的,遍歷的結果就是接收到的數據。數據類型就是通道的數據類型。通過for遍歷獲得的變量只有一個,即上面例子中的 data。

遍歷通道數據的例子請參考下面的代碼。

使用 for 從通道中接收數據:
package main

import (
    "fmt"

    "time"
)

func main() {

    // 構建一個通道
    ch := make(chan int)

    // 開啟一個并發匿名函數
    go func() {

        // 從3循環到0
        for i := 3; i >= 0; i-- {

            // 發送3到0之間的數值
            ch <- i

            // 每次發送完時等待
            time.Sleep(time.Second)
        }

    }()

    // 遍歷接收通道數據
    for data := range ch {

        // 打印通道數據
        fmt.Println(data)

        // 當遇到數據0時, 退出接收循環
        if data == 0 {
                break
        }
    }

}
執行代碼,輸出如下:
3
2
1
0

代碼說明如下:
  • 第 12 行,通過 make 生成一個整型元素的通道。
  • 第 15 行,將匿名函數并發執行。
  • 第 18 行,用循環生成 3 到 0 之間的數值。
  • 第 21 行,將 3 到 0 之間的數值依次發送到通道 ch 中。
  • 第 24 行,每次發送后暫停 1 秒。
  • 第 30 行,使用 for 從通道中接收數據。
  • 第 33 行,將接收到的數據打印出來。
  • 第 36 行,當接收到數值 0 時,停止接收。如果繼續發送,由于接收 goroutine 已經退出,沒有 goroutine 發送到通道,因此運行時將會觸發宕機報錯。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo