Go中的Channel(一) | 青训营笔记

67 阅读2分钟

管道 vs 共享内存

声明方法

  • make(chan int) 无缓冲
  • make(chan bool, 0) // 无缓冲
  • make(chan string, 2) // 有缓冲

基本用法

  • ch <- x // 发送数据
  • x = <- ch // 接收数据, 赋给x
  • <- ch // 接收数据, 并丢弃

内存与通信

  • 不要通过共享内存的方式进行通信
  • 而是应该通过通信的方式共享内存

为什么使用管道

  • 避免协程竞争和数据冲突的问题
  • 更高级的抽象,降低开发难度,增加程序可读性
  • 模块之间更容易解耦,增强扩展性和可维护性

如何设计

Pasted image 20230521221828.png

Pasted image 20230521222033.png

缓存区

Pasted image 20230521222819.png

type hchan struct {
	// 指示channel是开放还是关闭
	closed uint32 // 0开启, 1关闭
	
	// 组成一个缓存区
	qcount uint
	dataqsiz uint
	buf unsafe.Pointer
	elemsize uint16
	elemtype *_type

	// 两个队列
	recvx uint
	recvq waitq // 链表
	
	sendx uint
	sendq waitq

	// 保护hchan所有字段
	lock mutex
}
  • 环形缓存可以大幅降低GC的开销
  • 互斥锁并不是用来排队发送/接收数据
  • 互斥锁保护的hchan结构体本身
  • Channel并不是无锁的

发送数据的底层原理

c<-关键字

  • c<-关键字是一个语法糖
  • 编译阶段, 会把 c<-转化为runtime.chansend1()
  • chansend1()会调用chansend()方法

发送情形

Pasted image 20230521223417.png

直接发送

Pasted image 20230521223523.png 发送数据前, 已经有G在休眠等待接收 此时缓存肯定是空的, 不用考虑缓存 将数据直接拷贝给G的接收变量, 唤醒G 实现: 1. 从队列里去除一个等待接收的G 2. 将数据直接拷贝到接收变量中 3. 唤醒G

放入缓存

Pasted image 20230521224355.png

没有G在休眠等待, 但是有缓存空间 将数据放入缓存 实现:

  1. 获取可存入的缓存地址
  2. 存入数据
  3. 维护索引

休眠等待

Pasted image 20230521224641.png

没有G在休眠等待, 而且没有缓存或满了 自己进入发送队列, 休眠等待 实现:

  1. 把自己包装成sudog
  2. sudog放入sendq队列
  3. 休眠并解锁
  4. 被唤醒后, 数据已经被取走, 维护其它数据

总结

  • 编译阶段,会把<-转化为runtime.chansend1()
  • 1.直接发送时,将数据直接拷贝到目标变量
  • 2.放入缓存时,将数据放入环形缓存,成功返回
  • 3.休眠等待时,将自己包装后放入sendq,休眠