Channel是Go中的一個核心類型,你能夠把它當作一個管道,經過它併發核心單元就能夠發送或者接收數據進行通信(communication)。express
它的操做符是箭頭 <- 。緩存
ch <- v // 發送值v到Channel ch中 v := <-ch // 從Channel ch中接收數據,並將數據賦值給v
(箭頭的指向就是數據的流向)併發
就像 map 和 slice 數據類型同樣, channel必須先建立再使用:dom
ch := make(chan int)
Channel類型的定義格式以下:性能
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
它包括三種類型的定義。可選的<-
表明channel的方向。若是沒有指定方向,那麼Channel就是雙向的,既能夠接收數據,也能夠發送數據。ui
chan T // 能夠接收和發送類型爲 T 的數據 chan<- float64 // 只能夠用來發送 float64 類型的數據 <-chan int // 只能夠用來接收 int 類型的數據
<-
老是優先和最左邊的類型結合。(The <- operator associates with the leftmost chan possible)lua
chan<- chan int // 等價 chan<- (chan int) chan<- <-chan int // 等價 chan<- (<-chan int) <-chan <-chan int // 等價 <-chan (<-chan int) chan (<-chan int)
使用make
初始化Channel,而且能夠設置容量:spa
make(chan int, 100)
容量(capacity)表明Channel容納的最多的元素的數量,表明Channel的緩存的大小。
若是沒有設置容量,或者容量設置爲0, 說明Channel沒有緩存,只有sender和receiver都準備好了後它們的通信(communication)纔會發生(Blocking)。若是設置了緩存,就有可能不發生阻塞, 只有buffer滿了後 send纔會阻塞, 而只有緩存空了後receive纔會阻塞。一個nil channel不會通訊。code
能夠經過內建的close
方法能夠關閉Channel。隊列
你能夠在多個goroutine從/往 一個channel 中 receive/send 數據, 沒必要考慮額外的同步措施。
Channel能夠做爲一個先入先出(FIFO)的隊列,接收的數據和發送的數據的順序是一致的。
channel的 receive支持 multi-valued assignment,如
v, ok := <-ch
它能夠用來檢查Channel是否已經被關閉了。
ch <- 3
。SendStmt = Channel "<-" Expression . Channel = Expression .
在通信(communication)開始前channel和expression必選先求值出來(evaluated),好比下面的(3+4)先計算出7而後再發送給channel。
c := make(chan int) defer close(c) go func() { c <- 3 + 4 }() i := <-c fmt.Println(i)
send被執行前(proceed)通信(communication)一直被阻塞着。如前所言,無緩存的channel只有在receiver準備好後send才被執行。若是有緩存,而且緩存未滿,則send會被執行。
往一個已經被close的channel中繼續發送數據會致使run-time panic。
往nil channel中發送數據會一致被阻塞着。
<-ch
用來從channel ch中接收數據,這個表達式會一直被block,直到有數據能夠接收。從一個nil channel中接收數據會一直被block。
從一個被close的channel中接收數據不會被阻塞,而是當即返回,接收完已發送的數據後會返回元素類型的零值(zero value)。
如前所述,你可使用一個額外的返回參數來檢查channel是否關閉。
x, ok := <-ch x, ok = <-ch var x, ok = <-ch
若是OK 是false,代表接收的x是產生的零值,這個channel被關閉了或者爲空。
缺省狀況下,發送和接收會一直阻塞着,直到另外一方準備好。這種方式能夠用來在gororutine中進行同步,而沒必要使用顯示的鎖或者條件變量。
如官方的例子中x, y := <-c, <-c
這句會一直等待計算結果發送到channel中。
import "fmt" func sum(s []int, c chan int) { sum := 0 for _, 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 c fmt.Println(x, y, x+y) }
make的第二個參數指定緩存的大小:ch := make(chan int, 100)
。
經過緩存的使用,能夠儘可能避免阻塞,提供應用的性能。
for …… range
語句能夠處理Channel。
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
語句選擇一組可能的send操做和receive操做去處理。它相似switch
,可是隻是用來處理通信(communication)操做。
它的case
能夠是send語句,也能夠是receive語句,亦或者default
。
receive
語句能夠將值賦值給一個或者兩個變量。它必須是一個receive操做。
最多容許有一個default case
,它能夠放在case列表的任何位置,儘管咱們大部分會將它放在最後。
import "fmt" func fibonacci(c, quit chan int) { x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-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循環:
for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } }
select
有很重要的一個應用就是超時處理。 由於上面咱們提到,若是沒有case須要處理,select語句就會一直阻塞着。這時候咱們可能就須要一個超時操做,用來處理超時的狀況。
下面這個例子咱們會在2秒後往channel c1中發送一個數據,可是select
設置爲1秒超時,所以咱們會打印出timeout 1
,而不是result 1
。
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中。
咱們看一下關於時間的兩個Channel。
timer是一個定時器,表明將來的一個單一事件,你能夠告訴timer你要等待多長時間,它提供一個Channel,在未來的那個時間那個Channel提供了一個時間值。下面的例子中第二行會阻塞2秒鐘左右的時間,直到時間到了纔會繼續執行。
timer1 := time.NewTimer(time.Second * 2) <-timer1.C fmt.Println("Timer 1 expired")
固然若是你只是想單純的等待的話,可使用time.Sleep
來實現。
你還可使用timer.Stop
來中止計時器。
timer2 := time.NewTimer(time.Second) go func() { <-timer2.C fmt.Println("Timer 2 expired") }() stop2 := timer2.Stop() if stop2 { fmt.Println("Timer 2 stopped") }
ticker
是一個定時觸發的計時器,它會以一個間隔(interval)往Channel發送一個事件(當前時間),而Channel的接收者能夠以固定的時間間隔從Channel中讀取事件。下面的例子中ticker每500毫秒觸發一次,你能夠觀察輸出的時間。
ticker := time.NewTicker(time.Millisecond * 500) go func() { for t := range ticker.C { fmt.Println("Tick at", t) } }()
相似timer, ticker也能夠經過Stop
方法來中止。一旦它中止,接收者再也不會從channel中接收數據了。
內建的close方法能夠用來關閉channel。
總結一下channel關閉後sender的receiver操做。
若是channel c已經被關閉,繼續往它發送數據會致使panic: send on closed channel
:
import "time" func main() { go func() { time.Sleep(time.Hour) }() c := make(chan int, 10) c <- 1 c <- 2 close(c) c <- 3 }
可是從這個關閉的channel中不但能夠讀取出已發送的數據,還能夠不斷的讀取零值:
c := make(chan int, 10) c <- 1 c <- 2 close(c) fmt.Println(<-c) //1 fmt.Println(<-c) //2 fmt.Println(<-c) //0 fmt.Println(<-c) //0
可是若是經過range
讀取,channel關閉後for循環會跳出:
c := make(chan int, 10) c <- 1 c <- 2 close(c) for i := range c { fmt.Println(i) }
經過i, ok := <-c
能夠查看Channel的狀態,判斷值是零值仍是正常讀取的值。
c := make(chan int, 10) close(c) i, ok := <-c fmt.Printf("%d, %t", i, ok) //0, false
channel能夠用在goroutine之間的同步。
下面的例子中main goroutine經過done channel等待worker完成任務。 worker作完任務後只需往channel發送一個數據就能夠通知main goroutine任務完成。
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 }