單純地將函數(shù)并發(fā)執(zhí)行是沒(méi)有意義的峦朗,函數(shù)與函數(shù)之間需要交換數(shù)據(jù)才能體現(xiàn)并發(fā)執(zhí)行函數(shù)的作用淮逻。雖然可使用共享內(nèi)存進(jìn)行數(shù)據(jù)交換塞俱,但共享內(nèi)存在不同的Goroutine中容易發(fā)生竟態(tài)問(wèn)題飞涂。為了保證數(shù)據(jù)交換的正確性恩沛,必須使用互斥量對(duì)內(nèi)存進(jìn)行加鎖枣耀,但加鎖會(huì)變成串行勢(shì)必造成性能問(wèn)題霉晕。
Golang的并發(fā)模型是CSP(Communicating Sequential Processes),提倡通過(guò)通信共享內(nèi)存而非通過(guò)共享內(nèi)存實(shí)現(xiàn)通信捞奕。
Golang提倡使用通信的方式來(lái)代替共享內(nèi)存牺堰,當(dāng)一個(gè)資源需要在不同的Goroutine之間共享時(shí),Channel會(huì)在Goroutine之間架起一個(gè)管道颅围,以提供確保同步交換數(shù)據(jù)的機(jī)制伟葫。Goroutine是Golang程序并發(fā)的執(zhí)行體,Channel是Goroutine之間的通信機(jī)制院促。每個(gè)Channel都是一個(gè)通信機(jī)制筏养,可以讓一個(gè)Goroutine通過(guò)它給另外一個(gè)Goroutine發(fā)送值數(shù)據(jù)。Channel提供了一種機(jī)制在兩個(gè)并發(fā)執(zhí)行的函數(shù)之間進(jìn)行同步常拓,通過(guò)傳遞與該Channel元素類(lèi)型相符的值來(lái)進(jìn)行通信渐溶。
- Goroutine運(yùn)行在相同的地址空間,因此訪(fǎng)問(wèn)共享內(nèi)存必須做好同步弄抬。
- Channel是線(xiàn)程安全的茎辐,多個(gè)Goroutine訪(fǎng)問(wèn)時(shí)無(wú)需加鎖。
隊(duì)列
由于多個(gè)Goroutine為了爭(zhēng)搶數(shù)據(jù)勢(shì)必造成執(zhí)行的低效率,通道使用了一種類(lèi)似隊(duì)列的結(jié)構(gòu)荔茬,通過(guò)隊(duì)列以提高執(zhí)行的效率废膘。Golang中Channel是一種特殊的數(shù)據(jù)類(lèi)型,類(lèi)似一個(gè)傳送帶或隊(duì)列(本質(zhì)上是一個(gè)數(shù)據(jù)結(jié)構(gòu)-隊(duì)列)慕蔚,數(shù)據(jù)遵循先進(jìn)先出(FIFO, First In First Out)規(guī)則以保證收發(fā)數(shù)據(jù)順序丐黄。
通道聲明
channel
是Golang一種特殊的數(shù)據(jù)類(lèi)型,類(lèi)似UNIX系統(tǒng)中的管道或消息隊(duì)列孔飒,通過(guò)Goroutine可發(fā)送或接收數(shù)據(jù)進(jìn)行通訊灌闺。聲明channel
時(shí),需指定將要被共享數(shù)據(jù)的類(lèi)型坏瞄,channel
自身必須關(guān)聯(lián)一個(gè)數(shù)據(jù)類(lèi)型(需要一個(gè)類(lèi)型進(jìn)行修飾)桂对,即channel
可以發(fā)送數(shù)據(jù)的類(lèi)型。
var 變量 chan 類(lèi)型
channel
類(lèi)型也就是通道內(nèi)元素傳輸?shù)臄?shù)據(jù)類(lèi)型鸠匀,通道類(lèi)型的空值是nil
蕉斜。
var ch chan int
fmt.Printf("value = %v, type = %T, address = %p\n", ch, ch, &ch)
value = <nil>, type = chan int, address = 0xc000006028
通道初始化
channel
和map
類(lèi)型類(lèi)似,channel
擁有一個(gè)使用make()
創(chuàng)建的底層數(shù)據(jù)結(jié)構(gòu)的引用(分配內(nèi)存)缀棍,因此 channel
是引用類(lèi)型宅此,聲明后需要使用make
初始化后才能寫(xiě)入數(shù)據(jù),同時(shí)向channel
中寫(xiě)入數(shù)據(jù)時(shí)不能超過(guò)其容量爬范。
實(shí)例 := make(chan 元素類(lèi)型, [緩沖大小])
例如:聲明int
類(lèi)型的channel
跟伏,只能保存int
類(lèi)型的數(shù)據(jù)役耕,也就是說(shuō)一端只能向此channel
中放入int
類(lèi)型的數(shù)據(jù)套像,另一端只能從此channel
中讀取int
類(lèi)型的值景馁。
ch := make(chan int, 100)
channel
實(shí)例是通過(guò)make
創(chuàng)建的句柄,元素類(lèi)型則表示Channel內(nèi)傳輸?shù)臄?shù)據(jù)類(lèi)型斥难,當(dāng)作為參數(shù)或返回值時(shí)需指定為類(lèi)似ch chan int
的格式枝嘶。
例如:聲明channel
并分配內(nèi)存
var ch chan int
ch = make(chan int, 10)
fmt.Printf("value = %v, type = %T, address = %p\n", ch, ch, &ch)
value = 0xc00007e000, type = chan int, address = 0xc000006028
例如:查看channel
長(zhǎng)度和容量
var ch chan int
ch = make(chan int, 10)
fmt.Printf("value = %v, type = %T, address = %p, len = %d, cap = %d\n", ch, ch, &ch, len(ch), cap(ch))
value = 0xc00007e000, type = chan int, address = 0xc000006028, len = 0, cap = 10
通道緩存
使用make()
函數(shù)初始化channel
時(shí)可以設(shè)置容量(capacity),容量表示通道容納的最多的元素?cái)?shù)量蘸炸,即channel
的緩存大小躬络。
ch := make(chan datatype, capacity)
根據(jù)創(chuàng)建channel
時(shí)是否設(shè)置容量可將其分為兩種類(lèi)型,分別是unbuffered channel
和buffered channel
搭儒。
- 若沒(méi)有設(shè)置容量或容量為0說(shuō)明
channel
沒(méi)有緩存穷当,發(fā)送方和接收方都準(zhǔn)備完畢后通訊會(huì)發(fā)生阻塞(blocking)。 - 若設(shè)置了緩存即可能不會(huì)發(fā)生阻塞淹禾,只有緩存滿(mǎn)了之后發(fā)送時(shí)才會(huì)阻塞馁菜,只有緩存空了后接收方才會(huì)阻塞。而一個(gè)
nil
的通道是不會(huì)通信的铃岔。
容量 | 緩沖 | 阻塞 |
---|---|---|
capacity = 0 | 無(wú)緩沖 | 阻塞讀寫(xiě) |
capacity > 0 | 有緩沖 | 非阻塞汪疮,直到寫(xiě)滿(mǎn)capacity 個(gè)元素后才會(huì)阻塞寫(xiě)入峭火。 |
- 聲明
channel
時(shí)make(chan Type)
若沒(méi)有指定容量則相當(dāng)于make(chan Type, 0)
。
ch1 := make(chan int)
fmt.Printf("ch1: cap=%d\n", cap(ch1))//ch1: cap=0
ch2 := make(chan int, 0)
fmt.Printf("ch2: cap=%d\n", cap(ch2))//ch2: cap=0
通道操作
對(duì)通道的發(fā)送和接收操作都會(huì)在編譯期間轉(zhuǎn)換為底層的發(fā)送接收函數(shù)
通道操作 | 示例 | 描述 |
---|---|---|
發(fā)送 | ch<-10 |
將一個(gè)值發(fā)送到通道中 |
接受 | <-ch |
從一個(gè)通道中接收值智嚷,若接受后未賦值則會(huì)忽略結(jié)果卖丸。 |
關(guān)閉 | close(ch) |
關(guān)閉通道 |
示例 | 描述 |
---|---|
channel <- value |
發(fā)送value到channel |
<-channel |
接收并將其丟棄 |
val := <- channel |
從通道中接收數(shù)據(jù)并賦值給val
|
val, ok := <- channel |
從通道中接收數(shù)據(jù)并賦值給val ,同時(shí)檢查通道是否已關(guān)閉或是否為空盏道。 |
關(guān)閉通道
通道狀態(tài) | 描述 |
---|---|
nil | 未初始化的狀態(tài)稍浆,只進(jìn)行了聲明或手動(dòng)賦值為nil 。 |
active | 正常通道猜嘱,可讀或可寫(xiě)衅枫。 |
closed | 已關(guān)閉,關(guān)閉后通道的值并非為nil 朗伶。 |
關(guān)閉通道需調(diào)用Golang內(nèi)置的close()
函數(shù)來(lái)實(shí)現(xiàn)弦撩,需要注意的是,只有在通知接收方Goroutine所有數(shù)據(jù)都發(fā)送完畢時(shí)才需要關(guān)閉通道论皆。通道可以被垃圾回收機(jī)制回收益楼。和關(guān)閉文件不同的是,在結(jié)束操作之后關(guān)閉文件是必須的纯丸,但關(guān)閉通道卻不是偏形。
close(ch)
關(guān)閉通道操作原則上應(yīng)該由發(fā)送方完成静袖,若仍然向一個(gè)已關(guān)閉的通道發(fā)送數(shù)據(jù)則會(huì)導(dǎo)致程序拋出panic
觉鼻。若由接收者關(guān)閉通道也會(huì)觸發(fā)panic
風(fēng)險(xiǎn)。
package main
func main() {
c := make(chan int, 2)
close(c)
c <- 1// panic: send on closed channel
}
從一個(gè)已經(jīng)關(guān)閉的通道中讀取數(shù)據(jù)時(shí)需要注意的是队橙,接收者不會(huì)被一個(gè)已經(jīng)關(guān)閉的通道阻塞坠陈。接收者從關(guān)閉的通道中仍然可以讀取數(shù)據(jù),不過(guò)此時(shí)是通道的數(shù)據(jù)據(jù)類(lèi)型的默認(rèn)值捐康。此時(shí)可判斷讀取狀態(tài)仇矾,若為false
則表示通道已經(jīng)被關(guān)閉。
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 2)
go func() {
c <- 1
time.Sleep(time.Second)
c <- 2
time.Sleep(time.Second)
close(c)
}()
for i := 0; i < 4; i++ {
val, ok := <-c
fmt.Printf("receive %v status %t\n", val, ok)
}
}
receive 1 status true
receive 2 status true
receive 0 status false
receive 0 status false
上例中工作goroutine
關(guān)閉通道前解总,主goroutine
仍然會(huì)被工作goroutine
所阻塞贮匕,因此讀取數(shù)據(jù)時(shí),注意狀態(tài)位花枫。當(dāng)工作goroutine
關(guān)閉通道之后刻盐,主goroutine
仍然可以從通道中讀取int
類(lèi)型的默認(rèn)值0,只不過(guò)此時(shí)狀態(tài)變量會(huì)變?yōu)?code>false劳翰,而且不再被阻塞敦锌,直到循環(huán)結(jié)束。
通道方向
ChannelType = ("chan" | "chan" "<-" | "<-" "chan") ElementType .
方向 | 示例 |
---|---|
單向 | (chan<- | <-chan) ElementType |
雙向 | chan ElementType |
channel
類(lèi)型包括三種類(lèi)型的定義佳簸,可選的<-
代表channel
的方向乙墙,<-
優(yōu)先和最左側(cè)類(lèi)型結(jié)合。如果沒(méi)有指定方向,那么channel
就是雙向的听想,即可以接收數(shù)據(jù)腥刹,也可以發(fā)送數(shù)據(jù)。
chan T //可以接收和發(fā)送數(shù)據(jù)類(lèi)型為T(mén)的數(shù)據(jù)
chan<- float64 //僅用于發(fā)送float64類(lèi)型的數(shù)據(jù)
<-chan int //僅用于接收int類(lèi)型的數(shù)據(jù)
單向通道
所謂的單向通道是對(duì)通道的一種讀寫(xiě)使用限制汉买。單向通道只能寫(xiě)入或讀取數(shù)據(jù)肛走,由于通道本身是同時(shí)支持讀寫(xiě)的。
操作 | 示例 | 描述 |
---|---|---|
只寫(xiě) | var 實(shí)例 chan<- 元素類(lèi)型 |
聲明只能寫(xiě)入數(shù)據(jù)的通道 |
只讀 | var 實(shí)例 <-chan 元素類(lèi)型 |
聲明只能讀取數(shù)據(jù)的通道 |
例如:聲明channel
录别,設(shè)置只能單向?qū)懭搿?/p>
ch := make(chan int)
var senderChannel chan<- int = ch
例如:聲明只能單向?qū)懭氲?code>channel
ch := make(<-chan int)
同步阻塞
- 同步模式(阻塞):無(wú)緩沖的通道
unbuffered channel
無(wú)緩沖的通道只有當(dāng)發(fā)送方和接收方都準(zhǔn)備好時(shí)才會(huì)傳送數(shù)據(jù)朽色,發(fā)送方和接收方要同步就緒,只有在二者都ready
的情況下组题,數(shù)據(jù)才能在兩者之間傳輸(實(shí)際上就是內(nèi)存拷貝)葫男。否則任意一方先行發(fā)送或接收操作都會(huì)被掛起,等待另一方的出現(xiàn)才能被喚醒崔列。
例如:無(wú)緩沖通道發(fā)送時(shí)引發(fā)死鎖
var ch chan int
ch = make(chan int) //聲明無(wú)緩沖的通道梢褐,讀寫(xiě)阻塞。
ch <- 1 //發(fā)生阻塞
fatal error: all goroutines are asleep - deadlock!
例如:無(wú)緩沖通道讀取時(shí)引發(fā)死鎖
var ch chan int
ch = make(chan int) //聲明無(wú)緩沖的通道赵讯,讀寫(xiě)阻塞盈咳。
<-ch //發(fā)生阻塞
fatal error: all goroutines are asleep - deadlock!
例如:發(fā)送方和接收方要同步就緒,只有在二者都ready
的情況下边翼,數(shù)據(jù)才能在兩者之間傳輸(實(shí)際上就是內(nèi)存拷貝)鱼响。
var ch chan int
ch = make(chan int) //聲明無(wú)緩沖的通道,讀寫(xiě)阻塞组底。
ch <- 1 //發(fā)生阻塞
go func() {
<-ch
}()
fatal error: all goroutines are asleep - deadlock!
例如:無(wú)緩沖通道只有當(dāng)發(fā)送方和接收方都準(zhǔn)備好時(shí)才會(huì)傳送數(shù)據(jù)
var ch chan int
ch = make(chan int) //聲明無(wú)緩沖的通道丈积,讀寫(xiě)阻塞。
go func() {
<-ch
}()
ch <- 1 //不會(huì)阻塞
例如:為保證執(zhí)行順序添加sync.WaitGroup
var wg sync.WaitGroup
func main() {
var ch chan int
ch = make(chan int) //聲明無(wú)緩沖的通道债鸡,讀寫(xiě)阻塞江滨。
wg.Add(1)
go func() {
defer wg.Done()
v := <-ch
fmt.Printf("recieve:%d\n", v)
}()
v := 1
ch <- v //不會(huì)阻塞
fmt.Printf("send:%d\n", v)
wg.Wait()
}
由于無(wú)緩沖這種阻塞發(fā)送方和接收方的特性,使用時(shí)需要防止死鎖的發(fā)生厌均。如果在一個(gè)Goroutine內(nèi)向同一個(gè)Channel同時(shí)進(jìn)行讀取和發(fā)送則會(huì)導(dǎo)致死鎖唬滑。
例如:
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int)
go func() {
fmt.Println("work:ready to send")
c <- 1
fmt.Println("work:send 1 to channel")
fmt.Println("work:start sleep 1 second")
time.Sleep(time.Second)
fmt.Println("work:end sleep 1 second")
c <- 2
fmt.Println("work:send 2 to channel")
}()
fmt.Println("main:start sleep 1 second")
time.Sleep(time.Second)
fmt.Println("main:end sleep 1 second")
val := <-c
fmt.Println("main:receive value ", val)
val = <-c
fmt.Println("main:receive value ", val)
time.Sleep(time.Second)
}
main:start sleep 1 second
work:ready to send
main:end sleep 1 second
main:receive value 1
work:send 1 to channel
work:start sleep 1 second
work:end sleep 1 second
work:send 2 to channel
main:receive value 2
聲明無(wú)緩沖通道后開(kāi)啟goroutine
向通道發(fā)送數(shù)據(jù),然后主線(xiàn)程從通道中讀取數(shù)據(jù)棺弊。主線(xiàn)程休眠期間晶密,goroutine
阻塞在發(fā)送向通道發(fā)送數(shù)據(jù)的位置,只有當(dāng)主線(xiàn)程休眠結(jié)束開(kāi)始從通道中讀取數(shù)據(jù)時(shí)镊屎,goroutine
才開(kāi)始向下運(yùn)行惹挟。同時(shí),當(dāng)協(xié)程發(fā)送完第一個(gè)數(shù)據(jù)休眠時(shí)缝驳,主線(xiàn)程讀取了第一個(gè)數(shù)據(jù)连锯,準(zhǔn)備從通道中讀取第二個(gè)數(shù)據(jù)時(shí)會(huì)被阻塞归苍,直到協(xié)程休眠結(jié)束向通道發(fā)送數(shù)據(jù)后才會(huì)繼續(xù)運(yùn)行。
從無(wú)緩存的通道中讀取消息時(shí)會(huì)阻塞运怖,直到有goroutine
向該通道發(fā)送消息拼弃。同理,向無(wú)緩存的通道中發(fā)送消息時(shí)也會(huì)阻塞摇展,直到有goroutine
從通道中讀取消息吻氧。通過(guò)無(wú)緩存的通道進(jìn)行通信時(shí),接收者接收到的數(shù)據(jù)會(huì)發(fā)生在發(fā)送者喚醒之前咏连。
例如:
package main
import (
"fmt"
"time"
)
func send(ch chan int) {
ch <- 1
ch <- 2
ch <- 3
}
func receive(ch chan int) {
var recv int
for {
recv = <-ch
fmt.Println(recv)
}
}
func main() {
ch := make(chan int)
go send(ch)
go receive(ch)
time.Sleep(time.Second * time.Duration(2))
}
主函數(shù)中開(kāi)啟兩個(gè)goroutine
盯孙,一個(gè)用于執(zhí)行send()
函數(shù)用于每次向channel
中發(fā)送寫(xiě)入一個(gè)int
類(lèi)型的數(shù)值,一個(gè)receive()
函數(shù)用于每次從通道中讀取一個(gè)int
類(lèi)型的數(shù)值祟滴。當(dāng)channel
中沒(méi)有數(shù)據(jù)可讀時(shí)振惰,receive
的goroutine
會(huì)進(jìn)入阻塞狀態(tài),因?yàn)?code>receive中使用了for
無(wú)限循環(huán)垄懂,也就時(shí)說(shuō)receive
的goroutine
會(huì)一致阻塞下去骑晶,直到從channel
中讀取到數(shù)據(jù)。讀取到數(shù)據(jù)后又會(huì)進(jìn)入下一輪循環(huán)草慧,由被阻塞在recv = <-ch
上桶蛔。當(dāng)main
函數(shù)中的休眠時(shí)間到了指定時(shí)間后,main
程序會(huì)終止也就意味著主程序結(jié)束漫谷,此時(shí)所有的goroutine
都會(huì)停止執(zhí)行仔雷。
1
2
3
異步非阻塞
- 異步模式(非阻塞):有緩沖的通道
buffered channel
異步模式下,在緩沖槽可用的情況下抖剿,也就是擁有剩余容量的情況下朽寞,發(fā)送和接收操作都可以順序進(jìn)行。否則操作方同樣會(huì)被掛起斩郎,直到出現(xiàn)相反操作時(shí)才會(huì)被喚醒。
異步模式下喻频,緩沖槽要有剩余容量操作才會(huì)成功缩宜,否則也會(huì)被阻塞。
通多緩存的使用可以盡量避免堵塞甥温,以提供應(yīng)用的性能锻煌。
有緩存的通道類(lèi)似一個(gè)阻塞隊(duì)列(采用環(huán)形數(shù)組實(shí)現(xiàn)),當(dāng)緩存未滿(mǎn)時(shí)向通道中發(fā)送消息不會(huì)堵塞姻蚓,當(dāng)緩存滿(mǎn)時(shí)宋梧,發(fā)送操作將被阻塞,直到有其它goroutine
從中讀取消息狰挡。相應(yīng)的捂龄,當(dāng)通道中消息不為空時(shí)释涛,讀取消息不會(huì)出現(xiàn)堵塞,當(dāng)通道為空時(shí)倦沧,讀取操作會(huì)造成阻塞唇撬,直到有goroutine
向通道中寫(xiě)入消息。
有緩存的通道區(qū)別在于只有當(dāng)緩沖區(qū)被填滿(mǎn)時(shí)才會(huì)阻塞發(fā)送者展融,只有當(dāng)緩沖區(qū)為空時(shí)才會(huì)阻塞接收者窖认。
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 2)
go func() {
for i := 0; i < 4; i++ {
c <- i
fmt.Println("work:send ", i)
}
time.Sleep(time.Second * 5)
for i := 4; i < 6; i++ {
c <- i
fmt.Println("work:send ", i)
}
}()
for i := 0; i < 6; i++ {
time.Sleep(time.Second)
fmt.Println("main:receive ", <-c)
}
}
work:send 0
work:send 1
main:receive 0
work:send 2
main:receive 1
work:send 3
main:receive 2
main:receive 3
work:send 4
work:send 5
main:receive 4
main:receive 5
聲明容量為2帶緩沖的通道,開(kāi)啟一個(gè)協(xié)程告希,這個(gè)協(xié)程會(huì)向通道連續(xù)發(fā)送4個(gè)數(shù)據(jù)后然后休眠5秒扑浸,然后再向通道發(fā)送2個(gè)數(shù)據(jù)。而主線(xiàn)程則會(huì)從這個(gè)通道中讀取數(shù)據(jù)燕偶,每次讀取前會(huì)先休眠1秒首装。goroutine
首先向通道發(fā)送了兩個(gè)數(shù)據(jù)分別為0和1后被阻塞,因?yàn)榇藭r(shí)主線(xiàn)程在運(yùn)行1秒的休眠杭跪。主線(xiàn)程休眠結(jié)束后仙逻,從通道中讀取了第一個(gè)數(shù)據(jù)0后繼續(xù)休眠1秒。通道此時(shí)又有了緩沖涧尿,于是goroutine
又向通道發(fā)送了第三個(gè)數(shù)據(jù)2系奉,而后再次因?yàn)橥ǖ赖木彌_區(qū)已滿(mǎn)則進(jìn)入休眠。以此類(lèi)推姑廉,直到協(xié)程將4個(gè)數(shù)據(jù)發(fā)送完畢后缺亮,才開(kāi)始運(yùn)行5秒的休眠。而當(dāng)主線(xiàn)程從通道讀取完第4個(gè)數(shù)據(jù)也就是3之后桥言,當(dāng)準(zhǔn)備再?gòu)耐ǖ乐凶x取第五個(gè)數(shù)據(jù)時(shí)萌踱,由于通道為空,主線(xiàn)程作為接收者被阻塞号阿。直到goroutine
的5秒休眠結(jié)束并鸵,再次向通道中發(fā)送數(shù)據(jù)后,主線(xiàn)程讀取到數(shù)據(jù)而不被阻塞扔涧。
應(yīng)用
超時(shí)處理
channel
配合select
可實(shí)現(xiàn)多路復(fù)用园担,select
寫(xiě)法類(lèi)似switch
不同之處在于select
的每個(gè)case
代表一個(gè)通信操作,即在某個(gè)信道上進(jìn)行發(fā)送或接收的操作枯夜,同時(shí)會(huì)包含一些語(yǔ)句組成一個(gè) 語(yǔ)句塊弯汰。
select
用于多個(gè)信道監(jiān)聽(tīng)并收發(fā)消息,當(dāng)任何一個(gè)條件滿(mǎn)足時(shí)會(huì)執(zhí)行湖雹,若沒(méi)有可執(zhí)行的case
則會(huì)執(zhí)行默認(rèn)的case
咏闪。若不存在默認(rèn)的case
則程序發(fā)生堵塞。
select
默認(rèn)是堵塞的摔吏,只有監(jiān)聽(tīng)的信道中有發(fā)送或接收的數(shù)據(jù)時(shí)才會(huì)運(yùn)行鸽嫂。
生產(chǎn)者消費(fèi)者
生產(chǎn)者消費(fèi)者有一個(gè)著名的線(xiàn)程同步問(wèn)題纵装,即生產(chǎn)者產(chǎn)出后將產(chǎn)品交給若干消費(fèi)者,為使生產(chǎn)者和消費(fèi)者并發(fā)執(zhí)行溪胶,兩者之間會(huì)設(shè)置一個(gè)具有多個(gè)緩沖區(qū)的緩沖池搂擦,生產(chǎn)者將產(chǎn)出產(chǎn)品放入緩沖池,消費(fèi)者從緩沖池取出產(chǎn)品哗脖,此時(shí)生產(chǎn)者和消費(fèi)者之間必須保持同步瀑踢,即不允許消費(fèi)者到一個(gè)空緩沖區(qū)內(nèi)獲取產(chǎn)品,也不允許生產(chǎn)者向一個(gè)已經(jīng)存放產(chǎn)品的緩沖區(qū)中再次投放產(chǎn)品才避。
Go語(yǔ)言的channel
信道天生具有這種特性橱夭,即當(dāng)緩沖區(qū)滿(mǎn)時(shí)寫(xiě)空時(shí)讀都會(huì)被阻塞,另外channel
本身就是并發(fā)安全的桑逝。
使用單向信道創(chuàng)建生產(chǎn)者消費(fèi)者模式
package main
import "fmt"
func producer(out chan<- int) {
for i := 0; i < 10; i++ {
out <- i * i
}
close(out)
}
func consumer(in <-chan int) {
for num := range in {
fmt.Println("num = ", num)
}
}
func main() {
ch := make(chan int) //創(chuàng)建雙向信道
go producer(ch) //創(chuàng)建并發(fā)執(zhí)行單元作為生產(chǎn)者 生產(chǎn)數(shù)字寫(xiě)入信道
consumer(ch) //消費(fèi)者 從信道中讀取數(shù)據(jù) 打印輸出
}
num = 0
num = 1
num = 4
num = 9
num = 16
num = 25
num = 36
num = 49
num = 64
num = 81
生產(chǎn)者消費(fèi)者模式
package main
import (
"fmt"
"math/rand"
"time"
)
func producer(out chan<- string) {
for {
out <- fmt.Sprintf("%v", rand.Float64())
time.Sleep(time.Second * time.Duration(1))
}
}
func consumer(in <-chan string) {
for {
msg, ok := <-in //若信道無(wú)數(shù)據(jù)則發(fā)生堵塞
if !ok {
fmt.Println("channel close")
break
}
fmt.Println("msg = ", msg)
}
}
func main() {
ch := make(chan string, 5) //創(chuàng)建雙向信道
go producer(ch) //創(chuàng)建并發(fā)執(zhí)行單元作為生產(chǎn)者 生產(chǎn)數(shù)字寫(xiě)入信道
consumer(ch) //消費(fèi)者 從信道中讀取數(shù)據(jù) 打印輸出
}