1. 最好在主 goroutine 里定义channel并准备初始数据,再根据需要发出子goroutine 实现具体处理逻辑,各子goroutine可以从 channel 接收数据和(或)往 channel 发送数据。

  2. 如果不是特殊需要,不要定义无缓冲的 channal,也不要在主goroutine里从channel里接收或往其发送数据。

  3. 原则上,由发送方主动关闭channel, 接收方通过影子变量得知channel是否关闭。

  4. 尽量使用无阻塞的的select 轮询,也就是select 语句块包含一个default 分支。无阻塞的select只是说select语句块不会被阻塞,任一case从 channel 接收或发送数据都有可能阻塞,任一时刻当所有case分支阻塞时,走default分支。为了多次轮询若干个 channel, 可以使用 for loop 循环调用无阻塞的 select。

  5. 如果无阻塞的select语句块作用在 unbuffered channel 上,会由于channel一直阻塞,goroutine 会直接从 default 分支走掉。建议使用 buffered channel 并且 capacity 足够大。

  6. 无阻塞的 select 最好有超时实现。如有需要,可调用time包的time.NewTimer(n * time.Second).C 返回一个绝对到期时间的通道,通道里包含的是绝对到期时间值。

  7. 如果需要类似回调功能,可以定义结构体A,其嵌套一个 channel 叫 resultChan,请求方 goroutineA 发送A到某个 channel 叫 messageChan,处理方 goroutineB 从 messageChan 里接收 A,处理好后将结果发送到 resultChan,goroutineA 从 resultChan 接收最终结果然后处理,完成回调。

比较常用的无阻塞读取数据的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
aChannel := make(chan int, 3)
go func(){
DONE:
for { // for循环可以不断地从aChannel里读取数据,知道超时
select {
case e, ok := <- aChannel: // 影子变量判断通道是否关闭了
if ok {
fmt.Println("receive a value", e)
// handleReceivedValue(e)
} else {
fmt.Println("Channel had been closed.")
break DONE // 退出整个for循环
}
case <- time.NewTimer(5 * time.Second).C: // 调用time包里的官方超时实现,代码非常简洁
fmt.Println("Timeout")
break DONE
default: // default分支保证select语句不被阻塞
fmt.Println("no case fired, go default")
}
}
}()