golang 無(wú)緩存channel和有緩存channel
無(wú)緩存通道
var ch = make(chan int) // 創(chuàng)建一個(gè)int類(lèi)型的channel
cap(ch) // ch的容量是0
-
發(fā)送/存入
ch <- 1 // 存入一個(gè)int類(lèi)型的值
-
接收/取出
x := <-ch // 取出ch中的值潦牛,并賦值給x
-
關(guān)閉
close(ch) // 關(guān)閉發(fā)送方channel眶掌,對(duì)接收發(fā)channel關(guān)閉操作會(huì)panic val, ok := <-ch // ok 可用于判斷通道是否關(guān)閉。
| 操作 | nil | closed channel |
| :---: | :---: | :------------------------: |
| close | panic | panic |
| 存入 | 阻塞 | panic |
| 取出 | 阻塞 | 返回對(duì)應(yīng)類(lèi)型零值和關(guān)閉信號(hào) |
了解了channel的基本語(yǔ)法巴碗,我們看一個(gè)例子
```go
func main() {
ch := make(chan int) // 創(chuàng)建一個(gè)無(wú)緩存channel
ch <- 233
fmt.Println(<-ch)
}
上面的代碼可以成功執(zhí)行嗎朴爬?答案是否定的。會(huì)出現(xiàn)死鎖橡淆。這是因?yàn)?ch是一個(gè)無(wú)緩存的channel召噩,當(dāng)我們向它存入值的時(shí)候,會(huì)阻塞所在goroutine逸爵,這里當(dāng)然是main.main的goroutine蚣常,后面的語(yǔ)句根本不會(huì)執(zhí)行到。那么我們應(yīng)該怎么修改呢痊银,繼續(xù)看代碼:
func main() {
ch := make(chan int)
go func(chan int) { ch <- 233 }(ch)
fmt.Println(<-ch)
}
這里我們?cè)趍ain.main 主goroutine 中新建了一個(gè)goroutine抵蚊,這樣阻塞的就是這個(gè)goroutine了,不影響main.main溯革。當(dāng)主協(xié)程執(zhí)行到<-ch的時(shí)候贞绳,取出值,完成一次同步通信致稀。
一句話(huà)總結(jié):無(wú)緩存的channel只有在receiver準(zhǔn)備好后send才被執(zhí)行冈闭,也就是說(shuō)receiver的goroutine 和sender的goroutine 必須成對(duì)出現(xiàn)
之前看到一個(gè)打網(wǎng)球的模擬,很有趣抖单,可以加深一下理解萎攒,我們看一下
package main
import(
"math/rand"
"sync"
"time"
"fmt"
)
// wg用來(lái)等待程序結(jié)束
var wg sync.WaitGroup
func init() {
rand.Seed(time.Now().UnixNano())
}
// main是所有Go程序的入口
func main() {
// 創(chuàng)建一個(gè)無(wú)緩沖的通道
court := make(chan int)
// 計(jì)數(shù)加2,表示要等待兩個(gè)goroutine
wg.Add(2)
// 啟動(dòng)兩個(gè)選手
go player("Nick", court)
go player("Jack", court)
// 發(fā)球
court <- 1
// 等待游戲結(jié)束
wg.Wait()
}
// player 模擬一個(gè)選手在打網(wǎng)球
func player(name string, court chan int) {
// 在函數(shù)退出時(shí)調(diào)用Done來(lái)通知main函數(shù)工作已經(jīng)完成
defer wg.Done()
for{
// 等待球被擊打過(guò)來(lái)
ball, ok := <-court
if !ok {
// 如果通道被關(guān)閉矛绘,我們就贏了
fmt.Printf("Player %s Won\n", name)
return
}
// 選隨機(jī)數(shù)耍休,然后用這個(gè)數(shù)來(lái)判斷我們是否丟球
n := rand.Intn(100)
if n%5 == 0 {
fmt.Printf("Player %s Missed\n", name)
// 關(guān)閉通道,表示我們輸了
close(court)
return
}
// 顯示擊球數(shù)货矮,并將擊球數(shù)加1
fmt.Printf("Player %s Hit %d\n", name, ball)
ball++
// 將球打向?qū)κ? court <- ball
}
}
E:\go\src\learngolang\00test>go run main.go
Player Jack Hit 1
Player Nick Hit 2
Player Jack Hit 3
Player Nick Hit 4
Player Jack Hit 5
Player Nick Hit 6
Player Jack Hit 7
Player Nick Hit 8
Player Jack Hit 9
Player Nick Missed
Player Jack Won
上面的go函數(shù)為什么能交替執(zhí)行呢羊精?首先,我們有main主協(xié)程g0囚玫,里面創(chuàng)建了兩個(gè)goroutine喧锦,g1,g2抓督,然后燃少,主協(xié)程g0向無(wú)緩存channel中發(fā)送數(shù)據(jù),g0阻塞铃在,這時(shí)阵具,g1、g2中某一個(gè),如g2怔昨,先進(jìn)入通道的接收隊(duì)列,則通道獲得一次同步宿稀,g0取消阻塞趁舀。在player函數(shù)中court <- ball
這句代碼繼續(xù)向通道發(fā)送數(shù)據(jù),g2阻塞祝沸,這時(shí)通道中g(shù)1等待接收矮烹,g1開(kāi)始執(zhí)行,通道接收操作之后罩锐,g2取消阻塞奉狈,for循環(huán)繼續(xù)等待接收,阻塞涩惑,g1最后向通道發(fā)送數(shù)據(jù)仁期,g1阻塞;g2接收數(shù)據(jù)....直到goroutine結(jié)束運(yùn)行竭恬。
WaitGroup是用來(lái)讓g0等待g1跛蛋、g2執(zhí)行的。因?yàn)楫?dāng)程序執(zhí)行到一條go語(yǔ)句時(shí)痊硕,Go語(yǔ)言的運(yùn)行時(shí)系統(tǒng)赊级,會(huì)試圖從某個(gè)存放空閑的G隊(duì)列中獲取一個(gè)G(也就是goroutine),沒(méi)有則創(chuàng)建一個(gè)新的G岔绸。Go語(yǔ)言運(yùn)行時(shí)系統(tǒng)會(huì)用這個(gè)G去包裝當(dāng)前的那個(gè)go函數(shù)(或者說(shuō)該函數(shù)中的那些代碼)理逊,然后再把這個(gè)G追加到某個(gè)存放可運(yùn)行的G的隊(duì)列中。這類(lèi)隊(duì)列中的G總是會(huì)按照先入先出的順序盒揉,很快地由運(yùn)行時(shí)系統(tǒng)內(nèi)部的調(diào)度器安排運(yùn)行晋被。雖然這會(huì)很快,但是由于上面所說(shuō)的那些準(zhǔn)備工作還是不可避免的刚盈,所以耗時(shí)還是存在的墨微。因此,go函數(shù)的執(zhí)行時(shí)間總是會(huì)明顯滯后于它所屬的go語(yǔ)句的執(zhí)行時(shí)間扁掸。不然翘县,主goroutine代碼執(zhí)行完關(guān)閉,其創(chuàng)建的其他goroutine還沒(méi)來(lái)得及執(zhí)行也就跟著關(guān)閉了谴分。
有緩存channel
var ch = make(chan int, 10) //創(chuàng)建一個(gè)有緩存的channel锈麸,容量是10,即可以傳入10個(gè)int類(lèi)型的值
發(fā)送
接收
-
關(guān)閉
func main() { ch := make(chan int, 10) for i := 0; i < 5; i++ { ch <- i + 1 // 向channel中傳入5 個(gè)值 } fmt.Println(len(ch)) // 5 // channel長(zhǎng)度為5 close(ch) // 關(guān)閉channel fmt.Println("--------------") for x := range ch { // range的截止條件是channel關(guān)閉牺蹄,即關(guān)閉前存入多少個(gè)值忘伞,range的范圍是多少,如果range的對(duì)象沒(méi)有關(guān)閉,則最終阻塞氓奈,引起panic fmt.Println(x) // 接收一個(gè)翘魄,channel的有效值少一個(gè) } fmt.Println("--------------") fmt.Println(len(ch)) // 0 // channel 長(zhǎng)度為0 fmt.Println(cap(ch)) // 10 // channel容量為10 }
操作 | nil | 滿(mǎn) | 空 | closed channel |
---|---|---|---|---|
close | panic | 關(guān)閉 | 關(guān)閉 | panic |
存入 | 阻塞 | 阻塞 | 成功發(fā)送 | panic |
取出 | 阻塞 | 成功接收 | 阻塞 | 空:返回類(lèi)型零值、false<br />非空:返回相應(yīng)值舀奶、true |
buffer沒(méi)滿(mǎn)則暑竟,寫(xiě)入\發(fā)送 不會(huì)阻塞;buffer不空育勺,則讀取\接收 不會(huì)阻塞但荤。發(fā)送和接收不是同步的。但多說(shuō)一句涧至,其實(shí)這種情況不太常用腹躁,原因也很簡(jiǎn)單。因?yàn)?strong>上下游的消費(fèi)情況是統(tǒng)一的南蓬,如果生產(chǎn)者生產(chǎn)的速度過(guò)快纺非,而消費(fèi)端跟不上的話(huà),即使把它先暫存在緩沖區(qū)當(dāng)中也沒(méi)什么用赘方,早晚還是會(huì)要阻塞的铐炫。