chan用於goroutine間的通訊 1、基礎 有緩衝通道和無緩衝通道
無緩衝通道:make(chan int)
有緩衝通道:make(chan int, 5) 無緩衝通道會導致發送和接收的goroutine同步化
主要體現在如下兩點:
- 無緩衝通道會使發送goroutine阻塞,直到對應的chan上有接收者
package mainimport ( "fmt" "time")var ch chan intfunc main(){ var i int = 9 ch = make(chan int) go waiting() fmt.Println("向chan發送,時間:",time.Now()) ch <- i fmt.Println("發送完畢,時間:", time.Now()) //ch <- i time.Sleep(10000000000)}func waiting(){ for { time.Sleep(10000000000)//接收協程等待10秒 i, ok := <-ch if !ok { fmt.Println("chan is close!") break } fmt.Println("recv:", i) } fmt.Println("waiting end!")}
輸出:
winterdeMacBook-Pro:chantest winter$ ./chantest1向chan發送,時間: 2017-12-07 16:12:04.856740287 +0800 CSTrecv: 9發送完畢,時間: 2017-12-07 16:12:14.857350085 +0800 CSTwinterdeMacBook-Pro:chantest winter$
同樣,無緩衝通道會使接收goroutine阻塞,直到對應的chan上有發送
package mainimport ( "fmt" "time")var ch chan intfunc main(){ var i int = 9 ch = make(chan int) go waiting() fmt.Println("向chan發送,時間:",time.Now()) time.Sleep(10000000000)//等待10秒後向chan發送資料 ch <- i fmt.Println("發送完畢,時間:", time.Now()) //ch <- i time.Sleep(10000000000)}func waiting(){ for { //time.Sleep(10000000000)//接收協程等待10秒 fmt.Println("開始接收chan中的資料,時間:", time.Now()) i, ok := <-ch fmt.Println("chan中有資料,時間:",time.Now()) if !ok { fmt.Println("chan is close!") break } fmt.Println("recv:", i) } fmt.Println("waiting end!")}
輸出:
winterdeMacBook-Pro:chantest winter$ ./chantest1向chan發送,時間: 2017-12-07 16:17:35.598050582 +0800 CST開始接收chan中的資料,時間: 2017-12-07 16:17:35.598060998 +0800 CST發送完畢,時間: 2017-12-07 16:17:45.602548482 +0800 CSTchan中有資料,時間: 2017-12-07 16:17:45.602599793 +0800 CSTrecv: 9開始接收chan中的資料,時間: 2017-12-07 16:17:45.602665344 +0800 CST
緩衝通道,相當與一個隊列,隊列的最大長度在建立時指定 一個空的chan,會導致接收goroutine阻塞 一個滿的chan,會導致發送goroutine阻塞
2、會導致程式掛掉的異常
nil通道的發送和接收
chan的零值是nil,沒有初始化的等於nil
package mainimport ( "fmt")func main(){ var ch chan int if ch == nil { fmt.Println("ch is nil") }}
輸出:
winterdeMacBook-Pro:chantest winter$ ./chantest1ch is nil
nil值的chan上的發送和接收會導致程式掛掉並提示“nil chan”
package mainimport ( "fmt")var ch chan intfunc main(){ var i int = 9 go waiting() ch <- i}func waiting(){ i:= <-ch fmt.Println("recv:", i)}
輸出:
winterdeMacBook-Pro:chantest winter$ ./chantest1fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send (nil chan)]:main.main() /Users/winter/code/go_project/src/test/chantest/chantest1.go:10 +0x64goroutine 5 [chan receive (nil chan)]:main.waiting() /Users/winter/code/go_project/src/test/chantest/chantest1.go:15 +0x50created by main.main /Users/winter/code/go_project/src/test/chantest/chantest1.go:9 +0x35
向沒有接收者的通道發送
向沒有接受者的通道發送資料,程式會掛掉,會提示“deadlock”
package mainimport ( "fmt")var ch chan intfunc main(){ var i int = 9 ch = make(chan int) //ch沒有接收者 ch <- i}func waiting(){ i:= <-ch fmt.Println("recv:", i)}
輸出:
winterdeMacBook-Pro:chantest winter$ ./chantest1fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:main.main() /Users/winter/code/go_project/src/test/chantest/chantest1.go:11 +0x7f
chan後的關閉(close)操作
在一個關閉後的chan上進行接收操作,將擷取所有已經發送的值,直到chan為空白,然後阻塞解除,會一直擷取到chan元素類型對應的零值。即從一個關閉的chan上讀取資料,永遠不會阻塞。
在接收端可以判斷chan是否關閉,但是沒有辦法在發送端判斷chan是否關閉,所以只能在發送端關閉chan。
關閉chan是非必需的,因為GC回收chan,不是根據chan是否關閉,而是根據是否可以訪問來決定的
一般情況下,不要關閉chan,因為關閉一個chan存在以下風險:
關閉一個已經關閉的chan會導致程式崩潰
關閉一個零值chan會導致程式崩潰
chan關閉後,發送操作會導致程式崩潰
package mainimport ( "fmt" "time")var ch chan intfunc main(){ var i int = 9 ch = make(chan int) go waiting() ch <- i close(ch) ch <- i time.Sleep(10000000000)}func waiting(){ for { i:= <-ch fmt.Println("recv:", i) }}
輸出:
winterdeMacBook-Pro:chantest winter$ ./chantest1recv: 9recv: 0recv: 0recv: 0recv: 0recv: 0recv: 0panic: send on closed channelgoroutine 1 [running]:main.main() /Users/winter/code/go_project/src/test/chantest/chantest1.go:14 +0xda
可見,接收的goroutine收到最後一個元素後,一直擷取到int的零值。直到主goroutine往關閉的chan發送導致程式掛掉。
那麼如何判斷一個chan是否已經被close?從chan讀取資料,一般都簡寫成 i := <- ch,其實返回兩個值,例如i, ok := <-ch,第一個傳回值表示擷取的元素,第二個傳回值是bool型,等於false表示讀取異常。所以可以根據這個傳回值判斷chan是否被close。
package mainimport ( "fmt" "time")var ch chan intfunc main(){ var i int = 9 ch = make(chan int) go waiting() ch <- i close(ch) //ch <- i time.Sleep(10000000000)}func waiting(){ for { i, ok := <-ch if !ok { fmt.Println("chan is close!") break } fmt.Println("recv:", i) } fmt.Println("waiting end!")}