從兩道題看go channel的用法
在知乎看到有人分享了幾道筆試題,自己總結了一下其中與channel有關的題目。全部題目在這裏:
https://zhuanlan.zhihu.com/p/35058068
題目
5、請找出下面代碼的問題所在。
func main() {
ch := make(chan int, 1000)
// goroutine1
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
}()
// goroutine2
go func() {
for {
a, ok := <-ch
if !ok {
fmt.Println("close")
return
}
fmt.Println("a: ", a)
}
}()
close(ch)
fmt.Println("ok")
time.Sleep(time.Second * 100)
}
8、請說出下面代碼哪裏寫錯了
func main() {
abc := make(chan int, 1000)
for i := 0; i < 10; i++ {
abc <- i
}
// receiver goroutine
go func() {
for {
a := <-abc
fmt.Println("a: ", a)
}
}()
close(abc)
fmt.Println("close")
time.Sleep(time.Second * 100)
}
解釋
首先要明確這兩個要點:
- 向一個已經關閉的channel發送數據是會拋panic的,但是從一個已關閉的channel接收數據並不會拋panic,而是得到channel數據類型的零值。
- 同一個段程序每次啟動後goruntine的調度不一定相同,所以goroutine的執行順序有可能不一樣。
先看第一題,代碼運行時會拋出panic:
panic: send on closed channel
這是因為goroutine1 還沒把i 發送給ch,ch 就在main函數中被close,當把i 發送到ch 時就拋出了panic。同理goroutine2 中從已關閉的ch 中接收數據時ok 會返回false,然後就return了。而且由於goroutine 的執行順序不一樣,輸出close、ok、panic的順序也會不一樣,有興趣的話可以自己跑幾次。
那麽如果想讓程序正常輸出應該怎麽改呢,下面是其中一種方法:
func main() {
ch := make(chan int, 1000)
// goroutine1
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
// 只有一個goroutine向ch發送數據,可以考慮在這個goroutine中關閉。
close(ch)
}()
// goroutine2
go func() {
for {
a, ok := <-ch
if !ok {
fmt.Println("close")
return
}
fmt.Println("a: ", a)
}
}()
fmt.Println("ok")
time.Sleep(time.Second * 100)
}
再來看第二題,運行時輸出完0—9後會一直輸出0,直到sleep的時間結束。這是因為在把0—9發送給abc 後abc 就被關閉了,receiver goroutine 在接收完0—9後沒有跳出for 循環,而是一直從被關閉的abc 中接收數據,所以接收到的是abc 的零值——0。
有一種最簡單的辦法可以改正這個程序:
func main() {
abc := make(chan int, 1000)
for i := 0; i < 10; i++ {
abc <- i
}
go func() {
// 使用for-range,abc被關閉後會自動退出for循環。
for a := range abc {
fmt.Println("a: ", a)
}
}()
close(abc)
fmt.Println("close")
time.Sleep(time.Second * 10)
}
總結
使用channel時,
如果只有一個goroutine 向channel發送數據,可以在該goroutine 中close channel。當有多個goroutine 向channel 發送元素時close channel 的方法可以參考這篇文章:
https://www.jianshu.com/p/d24dfbb33781
用for-range 從channel 中接收數據代碼簡潔而且基本沒有副作用。
擴展閱讀
關於goroutine是如何調度的,可以參考這篇文章:
https://blog.csdn.net/liangzhiyang/article/details/52669851
References
http://colobu.com/2016/04/14/Golang-Channels/
從兩道題看go channel的用法