Go Channel 詳解

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

目錄 [−]

  1. Channel類型
  2. blocking
  3. Buffered Channels
  4. Range
  5. select
    1. timeout
  6. Timer和Ticker
  7. close
  8. 同步
  9. 參考資料

Channel是Go中的一個核心類型,你可以把它看成一個管道,通過它並發核心單元就可以發送或者接收資料進行通訊(communication)。

它的操作符是箭頭 <-

12
ch <- v    // 發送值v到Channel ch中v := <-ch  // 從Channel ch中接收資料,並將資料賦值給v

(箭頭的指向就是資料的流向)

就像 map 和 slice 資料類型一樣, channel必須先建立再使用:

1
ch := make(chan int)

Channel類型

Channel類型的定義格式如下:

1
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

它包括三種類型的定義。可選的<-代表channel的方向。如果沒有指定方向,那麼Channel就是雙向的,既可以接收資料,也可以發送資料。

123
chan T          // 可以接收和發送類型為 T 的資料chan<- float64  // 只可以用來發送 float64 類型的資料<-chan int      // 只可以用來接收 int 類型的資料

<-總是優先和最左邊的類型結合。(The <- operator associates with the leftmost chan possible)

1234
chan<- chan int    // 等價 chan<- (chan int)chan<- <-chan int  // 等價 chan<- (<-chan int)<-chan <-chan int  // 等價 <-chan (<-chan int)chan (<-chan int)

使用make初始化Channel,並且可以設定容量:

1
make(chan int, 100)

容量(capacity)代表Channel容納的最多的元素的數量,代表Channel的緩衝的大小。
如果沒有設定容量,或者容量設定為0, 說明Channel沒有緩衝,只有sender和receiver都準備好了後它們的通訊(communication)才會發生(Blocking)。如果設定了緩衝,就有可能不發生阻塞, 只有buffer滿了後 send才會阻塞, 而只有緩衝空了後receive才會阻塞。一個nil channel不會通訊。

可以通過內建的close方法可以關閉Channel。

你可以在多個goroutine從/往 一個channel 中 receive/send 資料, 不必考慮額外的同步措施。

Channel可以作為一個先入先出(FIFO)的隊列,接收的資料和發送的資料的順序是一致的。

channel的 receive支援 multi-valued assignment,如

1
v, ok := <-ch

它可以用來檢查Channel是否已經被關閉了。

  1. send語句
    send語句用來往Channel中發送資料, 如ch <- 3
    它的定義如下:
12
SendStmt = Channel "<-" Expression .Channel  = Expression .

在通訊(communication)開始前channel和expression必選先求值出來(evaluated),比如下面的(3+4)先計算出7然後再發送給channel。

12345
c := make(chan int)defer close(c)go func() { c <- 3 + 4 }()i := <-cfmt.Println(i)

send被執行前(proceed)通訊(communication)一直被阻塞著。如前所言,無緩衝的channel只有在receiver準備好後send才被執行。如果有緩衝,並且緩衝未滿,則send會被執行。

往一個已經被close的channel中繼續發送資料會導致run-time panic

往nil channel中發送資料會一致被阻塞著。

  1. receive 操作符
    <-ch用來從channel ch中接收資料,這個運算式會一直被block,直到有資料可以接收。

從一個nil channel中接收資料會一直被block。

從一個被close的channel中接收資料不會被阻塞,而是立即返回,接收完已發送的資料後會返回元素類型的零值(zero value)。

如前所述,你可以使用一個額外的返回參數來檢查channel是否關閉。

123
x, ok := <-chx, ok = <-chvar x, ok = <-ch

如果OK 是false,表明接收的x是產生的零值,這個channel被關閉了或者為空白。

blocking

預設情況下,發送和接收會一直阻塞著,知道另一方準備好。這種方式可以用來在gororutine中進行同步,而不必使用顯示的鎖或者條件變數。

如官方的例子中x, y := <-c, <-c這句會一直等待計算結果發送到channel中。

1234567891011121314151617181920
import "fmt"func sum(s []int, c chan int) {sum := 0for _, v := range s {sum += v}c <- sum // send sum to c}func main() {s := []int{7, 2, 8, -9, 4, 0}c := make(chan int)go sum(s[:len(s)/2], c)go sum(s[len(s)/2:], c)x, y := <-c, <-c // receive from cfmt.Println(x, y, x+y)}

Buffered Channels

make的第二個參數指定緩衝的大小:ch := make(chan int, 100)

通過緩衝的使用,可以盡量避免阻塞,提供應用的效能。

Range

for …… range語句可以處理Channel。

123456789101112131415161718
func main() {go func() {time.Sleep(1 * time.Hour)}()c := make(chan int)go func() {for i := 0; i < 10; i = i + 1 {c <- i}close(c)}()for i := range c {fmt.Println(i)}fmt.Println("Finished")}

range c產生的迭代值為Channel中發送的值,它會一直迭代知道channel被關閉。上面的例子中如果把close(c)注釋掉,程式會一直阻塞在for …… range那一行。

select

select語句選擇一組可能的send操作和receive操作去處理。它類似switch,但是只是用來處理通訊(communication)操作。
它的case可以是send語句,也可以是receive語句,亦或者default

receive語句可以將值賦值給一個或者兩個變數。它必須是一個receive操作。

最多允許有一個default case,它可以放在case列表的任何位置,儘管我們大部分會將它放在最後。

1234567891011121314151617181920212223242526
import "fmt"func fibonacci(c, quit chan int) {x, y := 0, 1for {select {case c <- x:x, y = y, x+ycase <-quit:fmt.Println("quit")return}}}func main() {c := make(chan int)quit := make(chan int)go func() {for i := 0; i < 10; i++ {fmt.Println(<-c)}quit <- 0}()fibonacci(c, quit)}

如果有同時多個case去處理,比如同時有多個channel可以接收資料,那麼Go會偽隨機的選擇一個case處理(pseudo-random)。如果沒有case需要處理,則會選擇default去處理,如果default case存在的情況下。如果沒有default case,則select語句會阻塞,直到某個case需要處理。

需要注意的是,nil channel上的操作會一直被阻塞,如果沒有default case,只有nil channel的select會一直被阻塞。

select語句和switch語句一樣,它不是迴圈,它只會選擇一個case來處理,如果想一直處理channel,你可以在外面加一個無限的for迴圈:

123456789
for {select {case c <- x:x, y = y, x+ycase <-quit:fmt.Println("quit")return}}

timeout

select有很重要的一個應用就是逾時處理。 因為上面我們提到,如果沒有case需要處理,select語句就會一直阻塞著。這時候我們可能就需要一個逾時操作,用來處理逾時的情況。
下面這個例子我們會在2秒後往channel c1中發送一個資料,但是select設定為1秒逾時,因此我們會列印出timeout 1,而不是result 1

1234567891011121314151617
import "time"import "fmt"func main() {    c1 := make(chan string, 1)    go func() {        time.Sleep(time.Second * 2)        c1 <- "result 1"    }()    select {    case res := <-c1:        fmt.Println(res)    case <-time.After(time.Second * 1):        fmt.Println("timeout 1")    }}

其實它利用的是time.After方法,它返回一個類型為<-chan Time的單向的channel,在指定的時間發送一個目前時間給返回的channel中。

Timer和Ticker

我們看一下關於時間的兩個Channel。
timer是一個定時器,代表未來的一個單一事件,你可以告訴timer你要等待多長時間,它提供一個Channel,在將來的那個時間那個Channel提供了一個時間值。下面的例子中第二行會阻塞2秒鐘左右的時間,直到時間到了才會繼續執行。

123
timer1 := time.NewTimer(time.Second * 2)<-timer1.Cfmt.Println("Timer 1 expired")

當然如果你只是想單純的等待的話,可以使用time.Sleep來實現。

你還可以使用timer.Stop來停止計時器。

123456789
timer2 := time.NewTimer(time.Second)go func() {<-timer2.Cfmt.Println("Timer 2 expired")}()stop2 := timer2.Stop()if stop2 {fmt.Println("Timer 2 stopped")}

ticker是一個定時觸發的計時器,它會以一個間隔(interval)往Channel發送一個事件(目前時間),而Channel的接收者可以以固定的時間間隔從Channel中讀取事件。下面的例子中ticker每500毫秒觸發一次,你可以觀察輸出的時間。

123456
ticker := time.NewTicker(time.Millisecond * 500)go func() {for t := range ticker.C {fmt.Println("Tick at", t)}}()

類似timer, ticker也可以通過Stop方法來停止。一旦它停止,接收者不再會從channel中接收資料了。

close

內建的close方法可以用來關閉channel。

總結一下channel關閉後sender的receiver操作。
如果channel c已經被關閉,繼續往它發送資料會導致panic: send on closed channel:

123456789101112
import "time"func main() {go func() {time.Sleep(time.Hour)}()c := make(chan int, 10)c <- 1c <- 2close(c)c <- 3}

但是從這個關閉的channel中不但可以讀取出已發送的資料,還可以不斷的讀取零值:

12345678
c := make(chan int, 10)c <- 1c <- 2close(c)fmt.Println(<-c) //1fmt.Println(<-c) //2fmt.Println(<-c) //0fmt.Println(<-c) //0

但是如果通過range讀取,channel關閉後for迴圈會跳出:

1234567
c := make(chan int, 10)c <- 1c <- 2close(c)for i := range c {fmt.Println(i)}

通過i, ok := <-c可以查看Channel的狀態,判斷值是零值還是正常讀取的值。

12345
c := make(chan int, 10)close(c)i, ok := <-cfmt.Printf("%d, %t", i, ok) //0, false

同步

channel可以用在goroutine之間的同步。
下面的例子中main goroutine通過done channel等待worker完成任務。 worker做完任務後只需往channel發送一個資料就可以通知main goroutine任務完成。

12345678910111213141516171819
import ("fmt""time")func worker(done chan bool) {time.Sleep(time.Second)// 通知任務已完成done <- true}func main() {done := make(chan bool, 1)go worker(done)// 等待任務完成<-done}

參考資料

  1. https://gobyexample.com/channels
  2. https://tour.golang.org/concurrency/2
  3. https://golang.org/ref/spec#Select_statements
  4. https://github.com/a8m/go-lang-cheat-sheet
  5. http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
  6. http://guzalexander.com/2013/12/06/golang-channels-tutorial.html

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.