Go Channel 詳細介紹
它的運算子是箭頭 <- 。
ch <- v // 傳送值v到Channel ch中 v := <-ch // 從Channel ch中接收資料,並將資料賦值給v
(箭頭的指向就是資料的流向)
就像 map 和 slice 資料型別一樣, channel必須先建立再使用:
ch := make(chan int)
Channel型別的定義格式如下:
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
它包括三種型別的定義。可選的<-代表channel的方向。如果沒有指定方向,那麼Channel就是雙向的,既可以接收資料,也可以傳送資料。
chan T // 可以接收和傳送型別為 T 的資料 chan<- float64 // 只可以用來傳送 float64 型別的資料 <-chan int // 只可以用來接收 int 型別的資料
<-總是優先和最左邊的型別結合。
chan<- chan int // 等價 chan<- (chan int) chan<- <-chan int // 等價 chan<- (<-chan int) <-chan <-chan int // 等價 <-chan (<-chan int) chan (<-chan int)
使用make初始化Channel,並且可以設定容量:
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,如
v, ok := <-ch
send語句
send語句用來往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中傳送資料會一致被阻塞著。
receive 運算子
<-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中。 [yiji]Timer和Ticker[/yiji] 我們看一下關於時間的兩個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 }
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901823/viewspace-2779894/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Go channel 介紹Go
- Go語言詳細介紹:logo和版本Go
- ApplicationContext 詳細介紹APPContext
- JDBC 詳細介紹JDBC
- javascript this詳細介紹JavaScript
- Git詳細介紹Git
- Ifconfig詳細介紹
- Kafka詳細介紹Kafka
- 一份儘可能全面的Go channel介紹Go
- SOLIDWORKS API詳細介紹SolidAPI
- Nacos 介面詳細介紹
- python字典詳細介紹Python
- Spring bean詳細介紹SpringBean
- Go中的有限狀態機FSM的詳細介紹Go
- Go語言開發的Web框架都有哪些?詳細介紹GoWeb框架
- 【SCN】Oracle SCN 詳細介紹Oracle
- Flutter系列(一)——詳細介紹Flutter
- Nginx服務詳細介紹Nginx
- LVM詳細介紹及建立LVM
- Webpack 打包 Javascript 詳細介紹WebJavaScript
- Http Module 的詳細介紹HTTP
- Java異常詳細介紹Java
- 註解的詳細介紹
- Dart語言詳解(一)——詳細介紹Dart
- oracle 大頁配置詳細介紹Oracle
- spring @component 的作用詳細介紹Spring
- pyhanlp 文字聚類詳細介紹HanLP聚類
- TreeViewTemplate移動元件詳細介紹View元件
- CJ/T188-2004 詳細介紹
- rqt的安裝及詳細介紹QT
- Qt 檔案模型(QFileSystemModel)詳細介紹QT模型
- WindowsPE重灌Windows系統詳細介紹Windows
- 超詳細的介紹Python語句Python
- 《非常防護》專案詳細介紹
- [Javascript] Promise ES6 詳細介紹JavaScriptPromise
- 超詳細 Java 15 新功能介紹Java
- 17. vue-route詳細介紹Vue
- JavaScript FormData的詳細介紹及使用JavaScriptORM