43

Go语言channel备忘录 | yoko blog

 4 years ago
source link: https://pengrl.com/p/23102/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

learn and live

Go语言channel备忘录

2019-07-16 | Go
| 1.3k
  1. 无缓冲channel等价于缓冲大小为0的channel,而不是1
  2. 发送者和接收者哪些情况会阻塞
  3. close哪些情况会导致panic
  4. 如何优雅的关闭channel
  5. 当一个select中有多个channel满足可读时,谁被激活
  6. select with default
  7. 读取时获取第二个返回值,以此判断该channel是否被关闭
  8. close前写入的数据,接收者依然可以按顺序读取到
  9. 一个channel有多个接收者时,close channel会唤醒所有接收者
  10. 配合timer实现channel读取的超时机制
  11. 当channel只用做同步通知,不关心channel中传输的值时,可使用 chan struct{} 类型
  12. 单向channel类型的作用
  13. 可以make单向channel,但是这样做没有意义
  14. channel配合for range的简化写法
1. 无缓冲channel等价于缓冲大小为0的channel,而不是1
1
2
3
4
5
6
7
8
9
10
var ch chan int // ch == nil

// 创建无缓冲channel
ch := make(chan int) // ch != nil
// 等价于
// ch := make(chan int, 0)
// 不等价于
// ch := make(chan int, 1)

close(ch) // close执行后, ch != nil
2. 发送者和接收者哪些情况会阻塞
  • 往值为nil的channel发送数据: 永久阻塞
  • 从值为nil的channel读取数据: 永久阻塞
  • 无缓冲模式的发送者: 阻塞直到数据被接收者接收
  • 无缓冲模式的接收者: 无数据可读时,阻塞
  • 有缓冲模式的发送者: 当缓冲满时,阻塞
  • 有缓冲模式的接收者: 无数据可读时,阻塞
3. close哪些情况会导致panic
  1. close值为nil的channel
  2. close已经被close的channel
  3. 向已经被close的channel发送数据
4. 如何优雅的关闭channel

需要特别注意:

  1. 接收者关闭channel要小心,因为关闭后发送者继续发送会panic
  2. 当有多个发送者时,在一个发送者协程中关闭channel要小心,因为关闭后其他发送者继续发送会panic

复杂情况下的参考思路:

  1. channel的关闭并非必须的,只要channel对象不再被持有,垃圾回收器会清理它
  2. 可使用原子变量等同步原语保证close有且只有发生一次
  3. 除了传输数据的channel,可以再增加channel配合select使用,用于取消生产、消费
  4. 接收端也可以通过其他channel发出消息,反向通知发送端
5. 当一个select中有多个channel满足可读时,谁被激活

Go随机选取一个满足条件的case分支执行,而不是按代码顺序选取。

1
2
3
4
5
6
7
8
9
10
11
// 如下代码段,可能输出ch1,也可能输出ch2
ch1 := make(chan int, 8)
ch2 := make(chan int, 8)
ch1 <- 1
ch2 <- 1
select {
case <- ch1:
fmt.Println("ch1")
case <- ch2:
fmt.Println("ch2")
}
6. select with default

当select中的条件都不满足时,会立即执行default分支

1
2
3
4
5
6
7
8
9
10
11
// 如下代码段,会立即打印default
ch1 := make(chan int, 1)
ch2 := make(chan int)
select {
case <- ch1:
fmt.Println("ch1")
case <- ch2:
fmt.Println("ch2")
default:
fmt.Println("default")
}
7. 读取时获取第二个返回值,以此判断该channel是否被关闭
1
2
v, ok := <- ch
// 当channel被关闭后,v为channel类型的零值,ok为false
8. close前写入的数据,接收者依然可以按顺序读取到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ch := make(chan int, 8)
ch <- 1
ch <- 2
ch <- 3
close(ch)

for {
v, ok := <- ch
fmt.Println(v, ok)
if !ok {
break
}
}
// 以上代码段,将打印出如下结果:
// 1 true
// 2 true
// 3 true
// 0 false
9. 一个channel有多个接收者时,close channel会唤醒所有接收者
10. 配合timer实现channel读取的超时机制

但在高性能场景需要注意,参见: golang源码阅读之定时器以及避坑指南

11. 当channel只用做同步通知,不关心channel中传输的值时,可使用 chan struct{} 类型

好处是语意上更正确,代码可读性更高。

1
2
3
4
5
// 初始化
ch := make(chan struct{}, 1)

// 写入
ch <- chan struct{}{}
12. 单向channel类型的作用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 比如Go系统库中Timer的实现,就使用了只读类型的channel,
// 目的是限制该channel在上层Timer对象只能读,不能写。这种限制是编译期的

// 提供给用户的Timer对象
type Timer struct {
C <-chan Time // 只读类型
r runtimeTimer
}

// 实际make的是chan Time类型
func NewTimer(d Duration) *Timer {
c := make(chan Time, 1)
t := &Timer{
C: c, // chan Time转换成只读类型,后续通过Timer对象访问C数据成员的操作只能读,不能写
r: runtimeTimer{
when: when(d),
f: sendTime,
arg: c, // 将chan Time类型传递给底层runtimeTimer中使用,底层可以写
},
}
startTimer(&t.r)
return t
}

// 用户调用After时,返回只读类型的channel
func After(d Duration) <-chan Time {
return NewTimer(d).C
}
13. 可以make单向channel,但是这样做没有意义
1
2
// 以下初始化了一个只写的channel是合法的,但是只能写,不能读,应该没有这种使用场景
ch := make(chan<- int, 4)
14. channel配合for range的简化写法
1
2
3
4
5
6
7
8
9
10
11
12
ch := make(chan int, 4)
ch <- 1
ch <- 2
close(ch)
for v := range ch {
fmt.Println(v)
}
fmt.Println("< for")
// 以上代码段将打印如下结果
// 1
// 2
// < for

本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/23102/

推荐阅读:

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK