Channels
創(chuàng)建channel
使用內(nèi)置的make函數(shù),我們可以創(chuàng)建一個(gè)channel
// ch has type 'chan int'
ch := make(chan int) // unbuffered channel
ch = make(chan int, 0) // unbuffered channel
ch = make(chan int, 3) // buffered channel with capacity 3
引用類型&零值&比較
- 和map類似,channel也對(duì)應(yīng)一個(gè)make創(chuàng)建的底層數(shù)據(jù)結(jié)構(gòu)的引用.當(dāng)我們復(fù)制一個(gè)channel或用于函數(shù)參數(shù)傳遞時(shí),我們只是拷貝了一個(gè)channel引用,因此調(diào)用者和被調(diào)用者將引用同一個(gè)channel對(duì)象.
- 和其它的引用類型一樣,channel的零值也是nil.
- 兩個(gè)相同類型的channel可以使用==運(yùn)算符比較
發(fā)送&接收
一個(gè)發(fā)送語(yǔ)句將一個(gè)值從一個(gè)goroutine通過(guò)channel發(fā)送到另一個(gè)執(zhí)行接收操作的goroutine
ch <- x // a send statement
x = <-ch // a receive expression in an assignment statement
<-ch // a receive statement; result is discarded
關(guān)閉channel
只有發(fā)送者需要關(guān)閉通道,接收者永遠(yuǎn)不會(huì)需要關(guān)閉通道
- 如果channel關(guān)閉,隨后對(duì)基于該channel的任何發(fā)送操作都將導(dǎo)致panic異常
- 對(duì)一個(gè)已經(jīng)被close過(guò)的channel進(jìn)行接收操作依然可以接受到之前已經(jīng)成功發(fā)送的數(shù)據(jù)
- 如果channel中已經(jīng)沒有數(shù)據(jù)的話將產(chǎn)生一個(gè)零值的數(shù)據(jù)
- 使用內(nèi)置的close函數(shù)就可以關(guān)閉一個(gè)channel:close(ch),通常是配合defer使用
其實(shí)你并不需要關(guān)閉每一個(gè)channel.
只有當(dāng)需要告訴接收者goroutine,所有的數(shù)據(jù)已經(jīng)全部發(fā)送時(shí)才需要關(guān)閉channel.
不管一個(gè)channel是否被關(guān)閉,當(dāng)它沒有被引用時(shí)將會(huì)被Go語(yǔ)言的垃圾自動(dòng)回收器回收.
關(guān)閉一個(gè)channels還會(huì)觸發(fā)一個(gè)廣播機(jī)制
所有channel接收者都會(huì)在channel關(guān)閉時(shí) 立刻從阻塞等待中返回 ok值為false 這個(gè)廣播機(jī)制經(jīng)常被利用進(jìn)行向多個(gè)訂閱者同時(shí)發(fā)送信號(hào) 例如退出信號(hào)
不帶緩存的Channels(同步channel)
基于無(wú)緩存Channels的發(fā)送和接收操作將導(dǎo)致兩個(gè)goroutine做一次同步操作
一個(gè)基于無(wú)緩存Channels的發(fā)送操作將導(dǎo)致發(fā)送者goroutine阻塞,直到另一個(gè)goroutine在相同的Channels上執(zhí)行接收操作.
當(dāng)發(fā)送的值通過(guò)Channels成功傳輸之后,兩個(gè)goroutine可以繼續(xù)執(zhí)行后面的語(yǔ)句.
如果接收操作先發(fā)生,那么接收者goroutine也將阻塞,直到有另一個(gè)goroutine在相同的Channels上執(zhí)行發(fā)送操作.
關(guān)于同步&并發(fā)
- 同步
當(dāng)我們說(shuō)x事件在y事件之前發(fā)生(happens before),我們并不是說(shuō)x事件在時(shí)間上比y時(shí)間更早,我們要表達(dá)的意思是要保證在此之前的事件都已經(jīng)完成了.
例如在此之前的更新某些變量的操作已經(jīng)完成,你可以放心依賴這些已完成的事件了 - 并發(fā)
當(dāng)我們說(shuō)x事件既不是在y事件之前發(fā)生也不是在y事件之后發(fā)生,我們就說(shuō)x事件和y事件是并發(fā)的疗绣。這并不是意味著x事件和y事件就一定是同時(shí)發(fā)生的,我們只是不能確定這兩個(gè)事件發(fā)生的先后順序.
func main() {
out := make(chan int)
// 這里向out發(fā)送數(shù)據(jù),但是并沒有另外一個(gè)goroutine來(lái)接收out的值
out <- 2
go func(in chan int) {
fmt.Println(<-in)
}(out)
// fatal error: all goroutines are asleep - deadlock!
}
消息事件
基于channels發(fā)送消息有兩個(gè)重要方面:
- 每個(gè)消息都有一個(gè)有意義的值,channel單純用來(lái)傳輸數(shù)據(jù)
- 有時(shí)候希望強(qiáng)調(diào)通訊發(fā)生的時(shí)刻時(shí),我們將它稱為消息事件.這些消息事件并不攜帶額外的信息,它僅僅是用作兩個(gè)goroutine之間的同步.這時(shí)候可以傳struct{}/bool/1這種值.
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
done := make(chan struct{})
go func() {
io.Copy(os.Stdout, conn) // NOTE: ignoring errors
log.Println("done")
done <- struct{}{} // signal the main goroutine
}()
mustCopy(conn, os.Stdin)
conn.Close()
<-done // wait for background goroutine to finish
}
帶緩存的Channels
帶緩存的Channel內(nèi)部持有一個(gè)元素隊(duì)列,隊(duì)列的最大容量是在調(diào)用make函數(shù)創(chuàng)建channel時(shí)通過(guò)第二個(gè)參數(shù)指定的.
向緩存Channel的發(fā)送操作就是向內(nèi)部緩存隊(duì)列的尾部插入元素,接收操作則是從隊(duì)列的頭部刪除元素.
如果內(nèi)部緩存隊(duì)列是滿的,那么發(fā)送操作將阻塞直到因另一個(gè)goroutine執(zhí)行接收操作而釋放了新的隊(duì)列空間.
如果channel是空的,接收操作將阻塞直到有另一個(gè)goroutine執(zhí)行!!!
串聯(lián)的Channels(Pipeline)
串聯(lián)Channels的管道可以用在需要長(zhǎng)時(shí)間運(yùn)行的服務(wù)中,每個(gè)長(zhǎng)時(shí)間運(yùn)行的goroutine可能會(huì)包含一個(gè)死循環(huán),在不同goroutine的死循環(huán)內(nèi)部使用串聯(lián)的Channels來(lái)通信.
檢測(cè)關(guān)閉的channel
如果發(fā)送者知道,沒有更多的值需要發(fā)送到channel的話,那么讓接收者也能及時(shí)知道沒有多余的值可接收將是有用的,因?yàn)榻邮照呖梢酝V共槐匾慕邮盏却?在發(fā)送方關(guān)閉channel即可.
但是在發(fā)送方關(guān)閉這個(gè)channel后,接收方依然會(huì)收到一個(gè)永無(wú)休止的零值序列.
- if v,ok:=<-ch;!ok{break}
- for v:=range ch{} // 當(dāng)channel被關(guān)閉并且沒有值可接收時(shí)跳出循環(huán)
goroutines泄漏,和垃圾變量不同,泄漏的goroutines并不會(huì)被自動(dòng)回收.因此確保每個(gè)不再需要的goroutine能正常退出是重要的!!!