开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第17天,点击查看活动详情
有限制的信道
回顾上文,只写信道例如(ch chan<- int),只读信道例如ch <-chan int,现实中我们可以使用专门的函数来往信道中写入数据,使用另外的函数从信道中读取数据。
如下:
package main
import (
"fmt"
)
// 使用专门的方法对信道进行读
func readChannel(ch <-chan int) {
for n := range ch {
fmt.Println(n)
}
}
// 使用专门的方法对信道进行写
func writeChannel(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}
func main() {
ch := make(chan int)
go writeChannel(ch)
go readChannel(ch)
fmt.Println("读取完毕")
}
细心的读者,可能就发现,上面的写法有错误,为什么?
因为go writeChannel(ch) 、go readChannel(ch) 、fmt.Println("读取完毕")是并发执行的,并没有顺序关系,当fmt.Println("读取完毕")执行完毕之后,main的go程结束,前面的两个子go程都结束,所以可能不会输出数字,甚至是数据的写入都没完成。我们可以做如下修改:
func main() {
ch := make(chan int)
ch2 := make(chan struct{})
go writeChannel(ch)
go func() {
readChannel(ch)
ch2 <- struct{}{}
}()
<-ch2
fmt.Println("读取完毕")
}
当然也可以使用waitGroup来进行,具体与用法可见***
除了使用waitGroup,也可以简单地在后面加上一条等待键盘输入的代码,如下:
var input string
fmt.Scanln(&input)
这样main的go程会被阻塞在fmt.Scanln(&input),直到用户输入数据才结束,所以上面的两个go程都能执行完毕。
select的用法
select有监听机制,监听case时,如果所有条件不满足则会进入阻塞状态,如果有一个满足,则执行它下面的语句,如果有多个满足,则会随机执行一个语句,另外,可以使用default来处理所有分支都不满足的情况。
package main
import "fmt"
// 使用专门的方法对信道进行写
func writeChannel(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
}
}
func writeChannel2(ch chan<- int) {
for i := 5; i < 10; i++ {
ch <- i
}
}
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go writeChannel(ch1)
go writeChannel2(ch2)
for {
select {
case n := <-ch1:
fmt.Println(n)
case n := <-ch2:
fmt.Println(n)
}
}
}
运行结果:
0
5
1
2
3
4
6
7
8
9
fatal error: all goroutines are asleep - deadlock!
可以看到,如果两个条件都满足,则会随机选择一个执行,出现dead block的原因是for循环肯定有一次会读取空信道,而我们的信道没有close,所以会引发dead block。