這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
What you are wasting today is tomorrow for those who died yesterday; what you hate now is the future you can not go back.
你所浪費的今天是昨天死去的人奢望的明天; 你所厭惡的現在是未來的你回不去的曾經。
Channel通道介紹
什麼是channel, channel可以理解為goroutine之間通訊的通道。一端發送資料方到達接收資料方的橋樑。
聲明通道
每個channel都有一個與之相關聯的類型, 這種類型是通道允許傳輸的資料類型。
channel的零值是nil, 一個nil channle沒有任何用途。因此必須使用類似map和slice的方式定義。
package mainimport "fmt"func main() { var A chan int if A == nil { fmt.Println("nil channle ,定義channle") A = make(chan int) fmt.Printf("Type of A is %T", A) }}
channel的發送和接收
chs := make(chan int)chs <- 0 // 寫入i := <- chs // 接收
發送、接收阻塞
channel的發送和接收預設是阻塞的。當從channel讀取資料時, main在讀取時阻塞,知道有資料寫入channel, 通道的發送也是如此。
通道的這個屬性可以使goroutine有效地進行通訊,而無需使用像其他程式設計語言中,很常見的顯式鎖或條件變數。
channel的使用
package mainimport ( "fmt" "time")func hello() { fmt.Println("Hello goroutine")}func main() { go hello() time.Sleep(1 * time.Second) fmt.Println("main function")}
在這裡我們使用sleep main進程的方式,阻塞main的繼續執行,好讓goroutine有時間執行 hello()內的內容。
但是這樣做並不能有效解決多個協程(main 和 goroutine)並行時出現的協程未執行完,main程退出問題。 如果hello()中的執行時間超過1S, 那麼sleep就無實際意義了。
下面使用channle來改善以上程式:
package mainimport ("fmt")var done chan boolfunc hello() {fmt.Println("Hello world goroutine")done <- true}func main() {done = make(chan bool)go hello()<-donefmt.Println("main function")}
首先main程會初始化一個channle, 然後go hello() 建立一個新的協程,並立即執行<-done (前面我們說過,只是簡單的調用,並不關心協程的輸出,而立刻返回繼續往下進行),並沒有發送資料到channel done,所以main程阻塞。直到goroutine執行完成並發送資料到channle,main才會繼續執行輸出。
還有一點要注意的是:channel的接受可以不用賦值到任何變數,即有效阻塞。
下面的程式會更好的理解channle:
package mainimport ("fmt""time")func hello(done chan bool) {fmt.Println("go hello() sleep ...")time.Sleep(4 * time.Second)fmt.Println("go hello() awake")done <- true}func main() {done := make(chan bool)fmt.Println("Main going to call hello go goroutine")go hello(done)<-donefmt.Println("Main received data")}
程式會在goroutine中sleep後繼續寫入channle。
package mainimport ("fmt")func add(number int, addChs chan int) {number = number + 10addChs <- number}func reduce(number int, reduceChs chan int) {number = number - 10reduceChs <- number}func main() {number := 589addChs := make(chan int)reduceChs := make(chan int)go add(number, addChs)go reduce(number, reduceChs)addVar, reduceVar := <-addChs, <-reduceChsfmt.Println(addVar, reduceVar)}
死結
當程式中只有channle的讀取或者發送的其中一個操作, 那麼程式就會發生Deadlock。
package mainfunc main() {ch := make(chan int)<-ch}
fatal error: all goroutines are asleep - deadlock!
單向通道
channel既可以發送也可以接收, 我們成為雙向channle。 如果channle只能接收或者發送,就稱為單向通道。
執行個體code說明一切:
package mainimport "fmt"func sendData(sendch chan<- int) { sendch <- 10}func main() { sendch := make(chan<- int) go sendData(sendch) fmt.Println(<-sendch)}
在mian程開始時,我們定義了一個唯寫(只發送)的channel, 但是後面main程又從中讀取。所以導致go程式編譯失敗。
在上面我們看到sendData()接收到一個唯寫的channel ,那麼如果我們傳入的是雙向的channel會發生什嗎?
package mainimport "fmt"func sendData(sendch chan<- int) { sendch <- 10}func main() { chnl := make(chan int) go sendData(chnl) fmt.Println(<-chnl)}
程式照常執行輸出。為什嗎?
可以這麼簡單的理解一下: 因為在方法內(像java 範圍)sendData限制了sendch通道的讀取,只能對channle進行寫入操作,但是main程不屬於同一範圍,sendData限制無效,main繼續讀取操作channle。
通道的關閉
發送端可以關閉通道,以便告訴接收方,不再發送資料。
接收方也可以在接收的同時,通過附加變數的方式檢測channle的關閉狀態。
v, ok := <- ch
如果開啟狀態下,ok為true, 否則為false。
在channel為關閉狀態時, v(接收到的值)也是有值的,即channle type( 通道類型 )的零值。 比如 make(chan int) 零值就是0, make(chan string)零值就是"", make(chan map[string]int)呢?大家可以聯想一下。
package mainimport ( "fmt")func producer(chnl chan int) { for i := 0; i < 10; i++ { chnl <- i } close(chnl)}func main() { ch := make(chan int) go producer(ch) // 試試將此處改用range代替 for { v, ok := <-ch if ok == false { break } fmt.Println("Received ", v, ok) }}
前面我們說過,<- channel 通道的讀取也會使程式阻塞,那麼range操作呢?
package mainimport ("fmt")func options(number int, dchnl chan int) {for i:= 0 ;i< 10 ;i++ {digit := number + idchnl <- digit}close(dchnl)}func box(number int, s chan int) {sum := 0dch := make(chan int)go options(number, dch)for digit := range dch {sum += digit + digit}s <- sum}func main(){s := make(chan int)go box(1, s)sVal := <-sfmt.Println("Final output: ",sVal)}
range讀取channle同樣可以阻塞程式,並不是單單<-channle。
The End.