Channel是Go中的一個核心類型,你可以把它看成一個管道,通過它併發核心單元就可以發送或者接收數據進行通訊(communication)。
類型
T表示任意的一種類型
- 雙向: chan T
- 單向僅發送: chan <-
- 單向僅接受: <- chan
單向的channel,不僅可以通過聲明make(chan <- interface{})
來創建,還可以通過隱身或顯示的通過 chan
來轉換,如下
func main() {
channel := make(chan int, 10)
convert(channel)
}
func convert(channel chan<- int) {}
在 convert函數中,就可以吧channel當成單向輸入管道來使用了
既然 雙向 chan,既可以接收,也可以發送,爲什麼還會有單向chan的存在? 我的一個理解便是 權限收斂,例如一個爬蟲系統中,有些進程a僅僅負責抓取頁面內容,並轉發給進程b,那進程a僅需要 單向發送的chan
即可
Blocking
缺省情況下,發送chan或接收chan會一直阻塞着,直到另一方準備好。這種方式可以用來在gororutine中進行同步,而不必使用顯示的鎖或者條件變量。
如官方的例子中x, y := <-c, <-c
這句會一直等待計算結果發送到channel中。以下面例子看一下
func bufferChannel() {
channel := make(chan int)
i := 0
go func(i int) {
fmt.Printf("start goroutine %d\n", i)①
channel <- i
fmt.Printf("send %d to channel\n", i)②
}(i)
time.Sleep(2 * time.Second)
fmt.Println("sleep 2 second")
value := <-channel③
fmt.Println("got ", value)
}
輸出結果如下
start goroutine 0
sleep 2 second
got 0
send 0 to channel
可以看出,go func
執行到了①後並沒有繼續執行②,而是等待③執行完成後,再去執行②,也就可以說明 channel <- i
阻塞了goroutine
的繼續執行
如果,我不想在這裏阻塞,而是我直接把數據放到channel
裏,等接收方準備好後,到channel
中自取自用如何處理,這裏就涉及到了另一個概念 buffered channel
buffered channel
我們把程序修改一下
func bufferChannel() {
channel := make(chan int, 1) // 這裏加了個參數
i := 0
go func(i int) {
fmt.Printf("start goroutine %d\n", i)①
channel <- i
fmt.Printf("send %d to channel\n", i)②
}(i)
time.Sleep(2 * time.Second)
fmt.Println("sleep 2 second")
value := <-channel③
fmt.Println("got ", value)
}
輸出結果
start goroutine 0
send 0 to channel
sleep 2 second
got 0
我們發現go func
執行完①之後就執行了②,並沒有等待③的執行結束,這就是buffered channel的效果了
我們只需要在make的時候,聲明底2個參數,也就是chan的緩衝區大小即可
通過上面的程序可以看出,我們一直在使用③的形成,即<- chan
來讀取chan中的數據,但是如果有多個goroutine在同時像一個chan寫數據,我們除了使用
for {
value <- chan
}
還有什麼更優雅的方式嗎
for … range
還是上面那個程序,我們使用 for … range 進行一下改造
func bufferChannel() {
channel := make(chan int, 1)
i := 0
go func(i int) {
fmt.Printf("start goroutine %d\n", i)
channel <- i
fmt.Printf("send %d to channel\n", i)
}(i)
time.Sleep(2 * time.Second)
fmt.Println("sleep 2 second")
for value := range channel {
fmt.Println("got ", value)
}
}
這樣就可以遍歷 channel
中的數據了,但是我們在運行的時候就會發現,哎 這個程序怎麼停不下來了?range channel
產生的迭代值爲Channel中發送的值,它會一直迭代直到channel被關閉,所以 我們goroutine
發送完數據後,把channel
關閉一下試試,這一次,我們不再進行time.Sleep(2 * time.Second)
func bufferChannel() {
channel := make(chan int, 1)
i := 0
go func(i int) {
fmt.Printf("start goroutine %d\n", i)
channel <- i
fmt.Printf("send %d to channel\n", i)
close(channel)
}(i)
for value := range channel {
fmt.Println("got ", value)
}
}
這樣,整個程序就可以正常退出了,所以,在使用range
的時候需要注意,如果channel
不關閉,則range
會一直阻塞在這裏的
select
我們上面講的一直都是隻有一個channel
的時候,我們應該怎麼去做,加入有兩個channel
或者更多的channel
,我們應該怎麼去做,這裏就介紹一下 go裏面的多路複用 select
,以下面程序爲例
func example() {
tick := time.Tick(time.Second)
after := time.After(3 * time.Second)
for {
select {
case <-tick:
fmt.Println("tick 1 second")
case <-after:
fmt.Println("after 3 second")
return
default:
fmt.Println("come into default")
time.Sleep(500 * time.Millisecond)
}
}
}
time.Tick
是go的time包提供的一個定時器的一個函數,它返回一個channel
,並在指定時間間隔內,向channel發送一條數據,time.Tick(time.Second)
就是每秒鐘向這個channel發送一個數據
time.After
是go的time包提供的一個定時器的一個函數,它返回一個channel
,並在指定時間間隔後,向channel發送一條數據,time.After(3 * time.Second)
就是3s後向這個channel發送一個數據
輸出結果
come into default
come into default
tick 1 second
come into default
come into default
tick 1 second
come into default
come into default
tick 1 second
after 3 second
可以看到,select
會選擇一個沒有阻塞的 channel
,並執行響應 case
下的邏輯,這樣就可以避免由於一個 channel
阻塞而導致後續的邏輯阻塞的情況了
我們繼續做個小實驗,把上面關閉的channel
放到 select
裏面試一下
func example() {
tick := time.Tick(time.Second)
after := time.After(3 * time.Second)
channel := make(chan int, 1)
go func() {
channel <- 1
close(channel)
}()
for {
select {
case <-tick:
fmt.Println("tick 1 second")
case <-after:
fmt.Println("after 3 second")
return
case value := <- channel:
fmt.Println("got", value)
default:
fmt.Println("come into default")
time.Sleep(500 * time.Millisecond)
}
}
}
輸出結果
.
.
.
.
got 0
got 0
got 0
got 0
got 0
after 3 second
簡直是車禍現場,幸好設置了3s主動退出,那case的時候,有沒有辦法判斷這個channel是否關閉了呢,當然是可以的,看下面的程序
func example() {
tick := time.Tick(time.Second)
after := time.After(3 * time.Second)
channel := make(chan int, 1)
go func() {
channel <- 1
close(channel)
}()
for {
select {
case <-tick:
fmt.Println("tick 1 second")
case <-after:
fmt.Println("after 3 second")
return
case value, ok := <- channel:
if ok {
fmt.Println("got", value)
} else {
fmt.Println("channel is closed")
time.Sleep(time.Second)
}
default:
fmt.Println("come into default")
time.Sleep(500 * time.Millisecond)
}
}
}
輸出結果
come into default
got 1
channel is closed
tick 1 second
channel is closed
channel is closed
after 3 second
綜上可以看出,通過 value, ok := <- channel
這種形式,ok獲取的就是用來判斷channel
是否關閉的,ok爲 true,表示channel正常,否則,channel就是關閉的