缓冲的Channels
缓冲:缓冲 channel 类似一个有容量的队列。当队列满的时候发送者会阻塞;当队列空的时候接收者会阻塞。
此外在使用channel之后可以进行关闭,关闭channel后,对该channel的任何发送操作都将导致panic异常。对一个已经被close过的channel之行接收操作依然可以接受到之前已经成功发送的数据;如果channel中已经没有数据的话讲产生一个零值的数据。
使用内置的close函数就可以关闭一个channel:
close(ch)
但是关于关闭channel 有几点需要注意:
- 重复关闭 channel 会导致 panic.
- 向关闭的 channel 发送数据会 panic.
- 从关闭的 channel 读数据不会 panic,读出channel中已有的数据之后再读就是channel类似的默认值,比如 chan int 类型的channel关闭之后读取到的值为 0.
这里我们需要区分一下第三种channel 中的值是默认值还是channel 关闭了。可以使用 ok-idiom 方式,这种方式在 map 中比较常用.
ch := make(chan int, 10)
...
close(ch)
// ok-idiom
val, ok := <-ch
if ok == false {
// channel closed
}
Channel的典型用法
goroutine 使用channel通信
func main() {
x := make(chan int)
go func() {
x <- 1
}()
<-x
}
select
select语句选择一组可能的send操作和receive操作去处理。它类似switch,但是只是用来处理通讯(communication)操作。 它的case可以是send语句,也可以是receive语句,亦或者default。
receive语句可以将值赋值给一个或者两个变量。它必须是一个receive操作。
select在一定程度上可以类比于linux中的 IO 多路复用中的 select。后者相当于提供了对多个 IO 事件的统一管理,而 Golang 中的 select 相当于提供了对多个 channel 的统一管理。当然这只是 select 在 channel 上的一种使用方法。
select {
case e, ok := <-ch1:
...
case e, ok := <-ch2:
...
default:
}
这里需要注意的是 select 中的 break 只能跳到 select 这一层。select 使用的时候一般需要配合 for 循环使用,因为正常 select 里面的流程也就执行一遍。这么来看 select 中的 break 就稍显鸡肋了。所以使用 break 的时候一般配置 label 使用,label 定义在 for循环这一层。
for {
select {
...
}
}
- range channel
使用range channel 我们可以直接取到 channel 中的值。当我们使用 range 来操作 channel 的时候,一旦 channel 关闭,channel 内部数据读完之后循环自动结束。
func consumer(ch chan int) {
for x := range ch {
fmt.Println(x)
...
}
}
func producer(ch chan int) {
for _, v := range values {
ch <- v
}
}
- 超时控制
select有很重要的一个应用就是超时处理。由于如果没有case需要处理,select语句就会一直阻塞着。这时候我们可能就需要一个超时操作,用来处理超时的情况通常在很多操作情况下都需要超时控制,我们可以利用 select 实现超时控制:
func main() {
c1 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
c1 <- "result 1"
}()
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(time.Second * 1):
fmt.Println("timeout 1")
}
}
这里利用的是time.After方法,它返回一个类型为<-chan Time的单向的channel,在指定的时间发送一个当前时间给返回的channel中。
- channel同步
channel可以用在goroutine之间的同步。 下面的例子main中goroutine通过done channel等待 mission完成任务。 mission做完任务后只需往channel发送一个数据就可以通知main goroutine任务完成。
func mission(done chan bool) {
time.Sleep(time.Second)
// 通知任务已完成
done <- true
}
func main() {
done := make(chan bool, 1)
go mission(done)
// 等待任务完成
<-done
}
在Golang中的channel 将goroutine 隔离开,并发编程的时候可以将注意力放在 channel 上。在一定程度上,这个和消息队列的解耦功能还是挺像的。