七叶笔记 » golang编程 » Golang Channel Select

Golang Channel Select

select 的实现在目前 go 的 master 分支已经有所修改,所以本文只针对 1.10 之前的版本。

基本用法

非阻塞 select:

 select {
    case <-ch:
    default:
}  

ch 中无数据时,不会阻塞,会直接走 default 分支。这种特性可以用来实现 trylock。

nil 进行发送或者接收都会永远阻塞,被包在 select 里也不例外:

 var a chan int
select {
    case <-ch:
}

println("can not be printed")  

for select 组合起来用很常见,不过需要注意,下面的两种场景可能会造成问题:

 for {
    select {
        case d := <-ch:
        default:
    }
}  

这种写法比较危险,如果 ch 中没有数据就会一直死循环 cpu 爆炸。

 for {
    select {
        case d := <-ch:
    }
}  

你以为这么写就没事了?啊当然,一般情况下还好。但如果 ch 被其它 goroutine close 掉了,那么 d:= <-ch 这种形式就是永远不阻塞,并且会一直返回零值了。如果不想关注这种情况,并且 select 中实际只操作一个 channel,建议写成 for range 形式:

 for d := range ch {
    // do some happy things with d
}  

这样 ch 关闭的时候 for 循环会自动退出。

如果 select 中需要监听多个 channel,并且这些 channel 可能被关闭,那需要老老实实地写双返回值的 channel 取值表达式:

 outer:
for {
    select {
        case d, ok := <-ch1:
            if !ok {
                break outer
            }
        case d, ok := <-ch2:
            if !ok {
                break outer
            }
    }
}  

当然,如果你不确定,可以用下面的 demo 进行验证:

 package main

import "time"

func main() {
    var ch1 chan int
    var ch2 = make(chan int)
    close(ch2)
    go func() {
        for {
            select {
            case d := <-ch1:
                println("ch1", d)
            case d := <-ch2:
                println("ch2", d)
            }
        }
    }()
    time.Sleep(time.Hour)
}  

尽管 ch2 已经被关闭,依然会不断地进入 case d:= <-ch2 中。因此在使用 for select 做设计时,请务必考虑当监听的 channel 在外部被正常或意外关闭后会有什么样的后果。

相关文章