《Go語言四十二章經(jīng)》第二十二章 通道(channel)
作者:李驍
22.1 通道(channel)
Go 奉行通過通信來共享內(nèi)存,而不是共享內(nèi)存來通信。所以,channel 是goroutine之間互相通信的通道,goroutine之間可以通過它發(fā)消息和接收消息。
channel是進(jìn)程內(nèi)的通信方式,因此通過channel傳遞對象的過程和調(diào)用函數(shù)時(shí)的參數(shù)傳遞行為比較一致,比如也可以傳遞指針等。
channel是類型相關(guān)的,一個(gè)channel只能傳遞(發(fā)送或接受 | send or receive)一種類型的值,這個(gè)類型需要在聲明channel時(shí)指定。
默認(rèn)的,信道的存消息和取消息都是阻塞的 (叫做無緩沖的信道)
使用make來建立一個(gè)通道:
var channel chan int = make(chan int)
// 或
channel := make(chan int)
Go中channel可以是發(fā)送(send)、接收(receive)、同時(shí)發(fā)送(send)和接收(receive)。
// 定義接收的channel
receive_only := make (<-chan int)
// 定義發(fā)送的channel
send_only := make (chan<- int)
// 可同時(shí)發(fā)送接收
send_receive := make (chan int)
- chan<- 表示數(shù)據(jù)進(jìn)入通道,要把數(shù)據(jù)寫進(jìn)通道,對于調(diào)用者就是發(fā)送。
- <-chan 表示數(shù)據(jù)從通道出來,對于調(diào)用者就是得到通道的數(shù)據(jù),當(dāng)然就是接收。
定義只發(fā)送或只接收的channel意義不大,一般用于在參數(shù)傳遞中:
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int) // 不使用帶緩沖區(qū)的channel
go send(c)
go recv(c)
time.Sleep(3 * time.Second)
close(c)
}
// 只能向chan里send數(shù)據(jù)
func send(c chan<- int) {
for i := 0; i < 10; i++ {
fmt.Println("send readey ", i)
c <- i
fmt.Println("send ", i)
}
}
// 只能接收channel中的數(shù)據(jù)
func recv(c <-chan int) {
for i := range c {
fmt.Println("received ", i)
}
}
程序輸出:
send readey 0
send 0
send readey 1
received 0
received 1
send 1
send readey 2
send 2
send readey 3
received 2
received 3
send 3
send readey 4
send 4
send readey 5
received 4
received 5
send 5
send readey 6
send 6
send readey 7
received 6
received 7
send 7
send readey 8
send 8
send readey 9
received 8
received 9
send 9
運(yùn)行結(jié)果上我們可以發(fā)現(xiàn)一個(gè)現(xiàn)象,往channel 發(fā)送數(shù)據(jù)后,這個(gè)數(shù)據(jù)如果沒有取走,channel是阻塞的,也就是不能繼續(xù)向channel 里面發(fā)送數(shù)據(jù)。因?yàn)樯厦娲a中,我們沒有指定channel 緩沖區(qū)的大小,默認(rèn)是阻塞的。
我們可以建立帶緩沖區(qū)的 channel:
c := make(chan int, 1024)
我們把前面的程序修改下:
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 10) // 使用帶緩沖區(qū)的channel
go send(c)
go recv(c)
time.Sleep(3 * time.Second)
close(c)
}
// 只能向chan里send發(fā)送數(shù)據(jù)
func send(c chan<- int) {
for i := 0; i < 10; i++ {
fmt.Println("send readey ", i)
c <- i
fmt.Println("send ", i)
}
}
// 只能接收channel中的數(shù)據(jù)
func recv(c <-chan int) {
for i := range c {
fmt.Println("received ", i)
}
}
程序輸出:
send readey 0
send 0
send readey 1
send 1
send readey 2
send 2
send readey 3
send 3
send readey 4
send 4
send readey 5
received 0
received 1
received 2
received 3
received 4
received 5
send 5
send readey 6
send 6
send readey 7
send 7
send readey 8
send 8
send readey 9
send 9
received 6
received 7
received 8
received 9
從運(yùn)行結(jié)果我們可以看到(每次執(zhí)行順序不一定相同,goroutine 運(yùn)行導(dǎo)致的原因),帶有緩沖區(qū)的channel,在緩沖區(qū)有數(shù)據(jù)而未填滿前,讀取不會出現(xiàn)阻塞的情況。
- 無緩沖的通道(unbuffered channel)是指在接收前沒有能力保存任何值的通道。
這種類型的通道要求發(fā)送 goroutine 和接收 goroutine 同時(shí)準(zhǔn)備好,才能完成發(fā)送和接收操作。如果兩個(gè)goroutine沒有同時(shí)準(zhǔn)備好,通道會導(dǎo)致先執(zhí)行發(fā)送或接收操作的 goroutine 阻塞等待。
這種對通道進(jìn)行發(fā)送和接收的交互行為本身就是同步的。
- 有緩沖的通道(buffered channel)是一種在被接收前能存儲一個(gè)或者多個(gè)值的通道。
這種類型的通道并不強(qiáng)制要求 goroutine 之間必須同時(shí)完成發(fā)送和接收。通道會阻塞發(fā)送和接收動(dòng)作的條件也會不同。只有在通道中沒有要接收的值時(shí),接收動(dòng)作才會阻塞。只有在通道沒有可用緩沖區(qū)容納被發(fā)送的值時(shí),發(fā)送動(dòng)作才會阻塞。
這導(dǎo)致有緩沖的通道和無緩沖的通道之間的一個(gè)很大的不同:無緩沖的通道保證進(jìn)行發(fā)送和接收的 goroutine 會在同一時(shí)間進(jìn)行數(shù)據(jù)交換;有緩沖的通道沒有這種保證。
如果給定了一個(gè)緩沖區(qū)容量,通道就是異步的。只要緩沖區(qū)有未使用空間用于發(fā)送數(shù)據(jù),或還包含可以接收的數(shù)據(jù),那么其通信就會無阻塞地進(jìn)行。
可以通過內(nèi)置的close函數(shù)來關(guān)閉channel實(shí)現(xiàn)。
channel不像文件一樣需要經(jīng)常去關(guān)閉,只有當(dāng)你確實(shí)沒有任何發(fā)送數(shù)據(jù)了,或者你想顯式的結(jié)束range循環(huán)之類的,才去關(guān)閉channel;
關(guān)閉channel后,無法向channel 再發(fā)送數(shù)據(jù)(引發(fā) panic 錯(cuò)誤后導(dǎo)致接收立即返回零值);
關(guān)閉channel后,可以繼續(xù)向channel接收數(shù)據(jù),不能繼續(xù)發(fā)送數(shù)據(jù);
對于nil channel,無論收發(fā)都會被阻塞。
本書《Go語言四十二章經(jīng)》內(nèi)容在github上同步地址:https://github.com/ffhelicopter/Go42
本書《Go語言四十二章經(jīng)》內(nèi)容在簡書同步地址: http://www.itdecent.cn/nb/29056963雖然本書中例子都經(jīng)過實(shí)際運(yùn)行,但難免出現(xiàn)錯(cuò)誤和不足之處,煩請您指出;如有建議也歡迎交流。
聯(lián)系郵箱:roteman@163.com