復(fù)習(xí)下Golang中的Channel學(xué)習(xí)使我快樂
譯自Channels in Go钞速,該文章實(shí)際分兩部分奥溺,下一部分是Channels in Go - range and select,譯文為Go中的Channel——range和select吏够,同樣在我的Golang文集中
goroutine允許我們并行的運(yùn)行一些代碼。但是要想讓這些代碼對我們來說更有意義恍涂,我們會有一些額外的需求--我們應(yīng)該能夠傳遞數(shù)據(jù)到正在運(yùn)行的進(jìn)程中;當(dāng)并行的進(jìn)程成功產(chǎn)生數(shù)據(jù)時植榕,我們應(yīng)該能從該進(jìn)程中獲取到數(shù)據(jù)再沧。channel配合goroutine提供了實(shí)現(xiàn)這些需求的途徑
channel可以想象為一個指定了大小和容量的管道或傳送帶。我們可以在其一邊放置內(nèi)容尊残,然后在另一邊獲取內(nèi)容
我們采用一個蛋糕制作裝箱工廠的例子來進(jìn)行下面的說明炒瘸。我們有一臺用于制作蛋糕的機(jī)器,還有一臺用于裝箱的機(jī)器寝衫。她們通過一條傳送帶互相連接--蛋糕機(jī)將蛋糕放上傳送帶顷扩,裝箱機(jī)在發(fā)現(xiàn)傳送帶上么有蛋糕時將其取走并裝入箱子
在go中,chan
關(guān)鍵字用于定義一個channel慰毅。make
關(guān)鍵字用于創(chuàng)建cahnnel隘截,創(chuàng)建時指定channel傳遞的數(shù)據(jù)類型
示例代碼1
ic := make(chan int) //a channel that can send and receive an int
sc := make(chan string) //a channel hat can send and receive a string
myc := make (chan my_type) //a channel for a custom defined struct type
在channel的變量名前面或后面,你可以使用<-
操作符來指示channel用于發(fā)送還是接收數(shù)據(jù)(注意對應(yīng)關(guān)系).假設(shè)事富,my_channel
是一個接收int
類型數(shù)據(jù)的channel技俐,你可以像my_channel <- 5
這樣向其發(fā)送數(shù)據(jù)乘陪,并且你可以像my_recvd_value <- my_channel
這樣來從中接收收據(jù)
想象channel是一個有方向的傳送帶:
從外部指向channel的箭頭用于向channel放置數(shù)據(jù)
從channel指向外部的箭頭用于從channel獲取數(shù)據(jù)
示例代碼2
my_channel := make(chan int)
//within some goroutine - to put a value on the channel
my_channel <- 5
//within some other goroutine - to take a value off the channel
var my_recvd_value int
my_recvd_value = <- my_channel
當(dāng)然统台,你也可以指定channel中的數(shù)據(jù)移動方向,只需要在創(chuàng)建channel時在chan
關(guān)鍵字旁使用<-
指明方向
示例代碼3
ic_send_only := make (<-chan int) //a channel that can only send data - arrow going out is sending
ic_recv_only := make (chan<- int) //a channel that can only receive a data - arrow going in is receiving
channel能夠保有的數(shù)據(jù)個數(shù)很重要啡邑。她能指示具體有多少條數(shù)據(jù)可以同時工作贱勃。即使發(fā)送者有能力產(chǎn)生很多條目,如果接受者沒有能力接收她們谤逼,那么她們就不能工作贵扰。這將會有很多蛋糕從傳送帶上掉落并浪費(fèi)掉ORZ。在并行計算中流部,這叫做生產(chǎn)者-消費(fèi)者同步問題(producer-consumer synchronization problem)
如果channel的容量(capacity)是1——也就是說戚绕,一旦有數(shù)據(jù)被放入channel,那么該數(shù)據(jù)必須被取走才能讓另一條數(shù)據(jù)放入枝冀,這就是同步channel(synchronous channel)舞丛。channel的每一邊——發(fā)送者和接受者——在同一時間只交流一條數(shù)據(jù),然后必須等待果漾,直到另一邊完成了相應(yīng)的發(fā)送或接收動作
目前為止球切,我們定義的所有的channel默認(rèn)都是同步channel,也就是說绒障,一條數(shù)據(jù)被放入channel后必須被取走才能再放置另一條數(shù)據(jù)《执眨現(xiàn)在,我們完成上面提到的蛋糕制作裝箱工廠户辱。由于channel在不同的goroutine之間交流數(shù)據(jù)鸵钝,我們有兩個名為makeCakeAndSend
和receiveCakeAndPack
的函數(shù)糙臼。每個函數(shù)都接收一個channel的引用作為參數(shù),這樣它們可以通過該channel進(jìn)行交流
示例代碼4
package main
import (
"fmt"
"time"
"strconv"
)
var i int
func makeCakeAndSend(cs chan string) {
i = i + 1
cakeName := "Strawberry Cake " + strconv.Itoa(i)
fmt.Println("Making a cake and sending ...", cakeName)
cs <- cakeName //send a strawberry cake
}
func receiveCakeAndPack(cs chan string) {
s := <-cs //get whatever cake is on the channel
fmt.Println("Packing received cake: ", s)
}
func main() {
cs := make(chan string)
for i := 0; i<3; i++ {
go makeCakeAndSend(cs)
go receiveCakeAndPack(cs)
//sleep for a while so that the program doesn’t exit immediately and output is clear for illustration
time.Sleep(1 * 1e9)
}
}
輸出結(jié)果
Making a cake and sending ... Strawberry Cake 1
Packing received cake: Strawberry Cake 1
Making a cake and sending ... Strawberry Cake 2
Packing received cake: Strawberry Cake 2
Making a cake and sending ... Strawberry Cake 3
Packing received cake: Strawberry Cake 3
在上述代碼中恩商,我們創(chuàng)建了三個制作蛋糕的函數(shù)調(diào)用弓摘,并在其之后立刻創(chuàng)建了三個裝箱蛋糕的函數(shù)調(diào)用。我們知道痕届,每當(dāng)一個蛋糕被裝箱韧献,就會有另一個蛋糕同時被制作并準(zhǔn)備好被裝箱。當(dāng)然如果你吹毛求疵研叫,代碼中確實(shí)有一個很輕微的含混之處——在打印Making a cake and sending …
和實(shí)際發(fā)送蛋糕到channel之間有延時锤窑。代碼中我們在每個循環(huán)中調(diào)用了time.Sleep()
,用于讓制作和裝箱動作一個接一個的發(fā)生嚷炉,這樣做是正確的渊啰。由于我們的channel是同步的,而且同一時間僅支持一條數(shù)據(jù)申屹,一個從channel中移除蛋糕的動作也就是一個裝箱動作绘证,必須在制作新蛋糕并將其放入channel之前發(fā)生
現(xiàn)在我們改動下上面的內(nèi)容讓其更像我們正常使用的代碼。典型的goroutine一般是一個包含了不斷循環(huán)的內(nèi)容的代碼塊哗讥,其內(nèi)部完成一些操作并且與其他的goroutine通過channel交換數(shù)據(jù)嚷那。在下面的例子中,我們將循環(huán)移至goroutine函數(shù)內(nèi)部杆煞,然后我們僅調(diào)用該goroutine一次
示例代碼5
package main
import (
"fmt"
"time"
"strconv"
)
func makeCakeAndSend(cs chan string) {
for i := 1; i<=3; i++ {
cakeName := "Strawberry Cake " + strconv.Itoa(i)
fmt.Println("Making a cake and sending ...", cakeName)
cs <- cakeName //send a strawberry cake
}
}
func receiveCakeAndPack(cs chan string) {
for i := 1; i<=3; i++ {
s := <-cs //get whatever cake is on the channel
fmt.Println("Packing received cake: ", s)
}
}
func main() {
cs := make(chan string)
go makeCakeAndSend(cs)
go receiveCakeAndPack(cs)
//sleep for a while so that the program doesn’t exit immediately
time.Sleep(4 * 1e9)
}
輸出結(jié)果
Making a cake and sending ... Strawberry Cake 1
Making a cake and sending ... Strawberry Cake 2
Packing received cake: Strawberry Cake 1
Packing received cake: Strawberry Cake 2
Making a cake and sending ... Strawberry Cake 3
Packing received cake: Strawberry Cake 3
輸出結(jié)果在我電腦上是這樣的魏宽,在你電腦上可能會不同,輸出結(jié)果依賴于你機(jī)器上goroutine的執(zhí)行順序决乎。如前所述队询,我們僅調(diào)用了每個goroutine一次,并且傳遞了一個公有的channel給她們构诚。在每個goroutine內(nèi)部有三個循環(huán)蚌斩,makeCakeAndSend
將蛋糕放入channel,receiveCakeAndPack
將蛋糕從channel中取出范嘱。由于程序會在我們創(chuàng)建了兩個goroutine后立即結(jié)束送膳,因此我們必須手動增加一個時間暫停操作來讓三個蛋糕都被制作和裝箱好
極其重要的一點(diǎn)是,我們必須理解彤侍,上面的輸出并沒有正確的反應(yīng)channel中實(shí)際的發(fā)送和接收操作肠缨。發(fā)送和接收在這里是同步的——同一時間僅有一個蛋糕。然而由于在打印語句和實(shí)際發(fā)送與接收間的延時盏阶,輸出看起來在順序上是錯誤的晒奕。而實(shí)際上發(fā)生的是:
Making a cake and sending ... Strawberry Cake 1
Packing received cake: Strawberry Cake 1
Making a cake and sending ... Strawberry Cake 2
Packing received cake: Strawberry Cake 2
Making a cake and sending ... Strawberry Cake 3 Packing received cake: Strawberry Cake 3
因此,一定要記住,在處理goroutine和channel時脑慧,通過打印日志分析執(zhí)行順序一定要萬分小心