?? 在golang中征冷,channel屬于較為核心的一個(gè)功能锭弊,尤其在go協(xié)程中,channel功能尤為重要。作為goroutine之間通信的一種方式窃页,channel跟Linux系統(tǒng)中的管道/消息隊(duì)列有很多類似之處俺榆。使用channel可以方便地在goroutine之間傳遞數(shù)據(jù)赞枕,此外垮斯,channel還關(guān)聯(lián)了數(shù)據(jù)類型,如int蜀涨、string等等瞎嬉,可以決定確定channel中的數(shù)據(jù)單元。
[TOC]
定義channel
?? Channel類型的定義格式如下厚柳,包括三種類型的定義氧枣。可選的<-
代表channel的方向别垮,如果沒有指定方向挑胸,那么Channel就是雙向的,既可以接收數(shù)據(jù)宰闰,也可以發(fā)送數(shù)據(jù)茬贵。
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
chan T // 可以接收和發(fā)送類型為 T 的數(shù)據(jù)
chan<- float64 // 只可以用來發(fā)送 float64 類型的數(shù)據(jù)
<-chan int // 只可以用來接收 int 類型的數(shù)據(jù)
// <-總是優(yōu)先和最左邊的類型結(jié)合簿透。
chan<- chan int // 等價(jià) chan<- (chan int)
chan<- <-chan int // 等價(jià) chan<- (<-chan int)
<-chan <-chan int // 等價(jià) <-chan (<-chan int)
chan (<-chan int)
?? 和slice、map類似解藻,可以使用make關(guān)鍵字來初始化channel老充。
unbuffered := make(chan int) //定義無緩沖的整型通道
buffered := make(chan string, 10) //有緩沖的字符串通道
??上述代碼中定義了一個(gè)無緩沖的channel和一個(gè)有緩沖channel。make
的第一個(gè)參數(shù)需要是關(guān)鍵字chan
螟左,之后跟著允許通道交換的數(shù)據(jù)的類型啡浊。如果創(chuàng)建的是一個(gè)有緩沖channel,之后還需要在第二個(gè)參數(shù)指定這個(gè)channel的緩沖區(qū)的大小胶背。和其他引用類型一樣巷嚣,channel 的空值為 nil
,使用 ==
可以對(duì)類型相同的 channel 進(jìn)行比較钳吟,只有指向相同對(duì)象或同為 nil
時(shí)廷粒,才返回 true
。
??無緩沖channel默認(rèn)會(huì)阻塞讀取操作红且,有緩沖channel能部分避免阻塞讀取操作坝茎。據(jù)此特性可以實(shí)現(xiàn)很多應(yīng)用場(chǎng)景,后文將會(huì)逐項(xiàng)介紹暇番。
讀寫channel
buffered <- "Gopher" //向channel buffered發(fā)送一個(gè)字符串
value := <-buffered //從channel buffered中接受一個(gè)字符串
??Go使用操作符->
實(shí)現(xiàn)channel的讀寫功能嗤放。需要注意的是,在執(zhí)行讀寫操作之前必須先初始化此通道壁酬,否則會(huì)出現(xiàn)永久阻塞的現(xiàn)象次酌。
關(guān)閉channel
??使用go內(nèi)置的close
函數(shù)可以關(guān)閉channel,實(shí)際使用中經(jīng)常使用 defer
功能舆乔,在程序最后關(guān)閉channel岳服。
close(buffered)
channel用處
gorouting通信
??這一點(diǎn)勿需多言,前面介紹channel時(shí)就提到這一點(diǎn)蜕煌,channel的下述特性均基于此項(xiàng)功能實(shí)現(xiàn)派阱。
gorouting同步
?? 對(duì)于unbuffered channel诬留,缺省情況下發(fā)送和接收會(huì)一直阻塞著斜纪,直至另一方做好準(zhǔn)備。用此特性可以實(shí)現(xiàn)gororutine之間的同步功能文兑,而不必使用顯式的鎖或條件變量盒刚。
package main
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)
}
?? 上述代碼執(zhí)行結(jié)果如下
kefin@localhost:~/gopath/src/iotest $ go run sync.go
-5 17 12
?? 上述代碼是官方提供的例子,x, y := <-c, <-c
這行代碼會(huì)一直處于阻塞狀態(tài)绿贞,直至計(jì)算結(jié)果發(fā)送到channel中因块。
用于range遍歷
package main
import (
"fmt"
"time"
)
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
產(chǎn)生的迭代值為Channel中發(fā)送的值,它會(huì)一直迭代知道channel被關(guān)閉籍铁。上面的例子中如果把close(c)
注釋掉涡上,程序會(huì)一直阻塞在for …… range那一行趾断。代碼的執(zhí)行結(jié)果為
kefin@localhost:~/gopath/src/iotest $ go run range.go
0
1
2
3
4
5
6
7
8
9
Finished
配合select使用
?? select語句選擇一組可能的send操作和receive操作去處理,它類似switch吩愧,但是只是用來處理通訊(communication)操作芋酌。 它的case可以是send語句,也可以是receive語句雁佳,亦或者default脐帝。receive語句可以將值賦值給一個(gè)或者兩個(gè)變量,最多允許有一個(gè)default case,它可以放在case列表的任何位置糖权,大部分會(huì)將它放在最后堵腹。
package main
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)
}
??如果有同時(shí)多個(gè)case去處理,比如同時(shí)有多個(gè)channel可以接收數(shù)據(jù)星澳,那么Go會(huì)偽隨機(jī)的選擇一個(gè)case
處理(pseudo-random)疚顷。如果沒有case需要處理,則會(huì)選擇default
去處理募判。如果沒有default case
荡含,則select語句會(huì)阻塞,直到某個(gè)case需要處理届垫。需要注意的是释液,nil
channel上的操作會(huì)一直被阻塞。如果沒有default case
装处,只有nil
误债,那么channel的select會(huì)一直被阻塞。
??此外妄迁,還可以配合select的超時(shí)處理功能寝蹈,如上所述,沒有case需要處理時(shí)登淘,select語句就會(huì)一直阻塞箫老,此時(shí)通常需要設(shè)置超時(shí)操作來處理超時(shí)的情況。 下面這個(gè)例子我們會(huì)在2秒后往channel c1中發(fā)送一個(gè)數(shù)據(jù)黔州,但是select設(shè)置為1秒超時(shí),因此我們會(huì)打印出timeout 1,而不是result 1耍鬓。
package main
import (
"fmt"
"time"
)
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")
}
}
??其實(shí)利用的是time.After方法,它返回一個(gè)類型為<-chan Time的單向的channel流妻,在指定的時(shí)間發(fā)送一個(gè)當(dāng)前時(shí)間給返回的channel中牲蜀。執(zhí)行結(jié)果為
kefin@localhost:~/gopath/src/iotest $ go run select_timeout.go
timeout 1
實(shí)現(xiàn)Timer和Ticker
??timer是一個(gè)定時(shí)器,代表未來的一個(gè)單一事件绅这,可以設(shè)置timer需要等待多長(zhǎng)時(shí)間涣达,它提供一個(gè)Channel,在將來的那個(gè)時(shí)間那個(gè)Channel提供了一個(gè)時(shí)間值。下面的例子中第二行會(huì)阻塞2秒鐘左右的時(shí)間度苔,直到時(shí)間到了才會(huì)繼續(xù)執(zhí)行匆篓。當(dāng)然如果只想單純的等待2秒,可以使用time.Sleep(2)
來實(shí)現(xiàn)寇窑。
timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")
你還可以使用timer.Stop
來停止[計(jì)時(shí)器]
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
是一個(gè)定時(shí)觸發(fā)的計(jì)時(shí)器奕删,它會(huì)以一個(gè)間隔(interval)往Channel發(fā)送一個(gè)事件(當(dāng)前時(shí)間),而Channel的接收者可以以固定的時(shí)間間隔從Channel中讀取事件疗认。下面的例子中ticker每500毫秒觸發(fā)一次完残,你可以觀察輸出的時(shí)間
ticker := time.NewTicker(time.Millisecond * 500)
go func() {
for t := range ticker.C {
fmt.Println("Tick at", t)
}
}()
同樣,ticker也可以通過Stop方法來停止横漏。一旦它停止谨设,接收者不再會(huì)從channel中接收數(shù)據(jù)了。
channel操作注意事項(xiàng)
- 關(guān)閉一個(gè)未初始化(nil) 的 channel 或者重復(fù)關(guān)閉同一個(gè)channel均會(huì)產(chǎn)生 panic
- 向一個(gè)已關(guān)閉的 channel 中發(fā)送消息會(huì)產(chǎn)生 panic
- 從已關(guān)閉的 channel 讀取消息不會(huì)產(chǎn)生 panic缎浇,且能讀出 channel 中還未被讀取的消息扎拣,若消息均已讀出,則會(huì)讀到類型的零值素跺。
- 從已關(guān)閉的 channel 中讀取消息永遠(yuǎn)不會(huì)阻塞二蓝,并且會(huì)返回 false ,據(jù)此可判斷 channel 是否關(guān)閉
- 關(guān)閉 channel 會(huì)產(chǎn)生一個(gè)廣播機(jī)制指厌,所有向 channel 讀取消息的 goroutine 都會(huì)收到消息