Channel(二)只读信道、只写信道以及select关键字

119 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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。