June's Studio.

Golang-通道Channel

字数统计: 860阅读时长: 3 min
2022/11/24

Do not communicate by sharing memory; instead, share memory by communicating.
不要通过共享内存来通信,而要通过通信来实现内存共享。

CSP(Communicating Sequential Processes)模型

数据结构

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
type hchan struct {

qcount uint // chan 里元素数量
dataqsiz uint // chan 底层循环数组的长度(有缓冲区channel最多存储多少个元素,无缓冲区为0)

buf unsafe.Pointer // 指向底层循环数组的指针 只针对有缓冲的 channel
elemsize uint16 // chan 中元素大小
elemtype *_type // chan 中元素类型的类型元数据 (因为golang运行时中,内存复制、垃圾回收等机制依赖数据的类型信息)

closed uint32 // chan 是否被关闭的标志

sendx uint // 已发送元素在循环数组中的索引
recvx uint // 已接收元素在循环数组中的索引

recvq waitq // 等待接收的 goroutine 队列
sendq waitq // 等待发送的 goroutine 队列

lock mutex // 用来保证每个读 channel 或写 channel 的操作都是原子的
}

type waitq struct { //waitq 是 sudog 的一个双向链表
first *sudog // sudog 实际上是对 goroutine 的一个封装
last *sudog
}

1
2
3
ch -< x // a send statement 7
x = <- ch // a receive expression in an assignment statement
<- ch // a receive statement ; result is discarded

向channel发送数据

发送操作: chansend()函数 ,func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool

如果检测到channel为nil,当前goroutine会被挂起

对于不阻塞的channel,快速检测失败场景:

  • channel无缓冲区,且等待接收队列里没有goroutine
  • channle有缓冲区,但循环数组已经满了
    代码表示为:
    1
    2
    3
    4
    if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||
    (c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {
    return false
    }

上锁 // 为什么加在快速检测失败判断后面:少获取一次锁,提升性能。

如果检测到channel已经关闭,直接panic
如果能从等待队列recvq里出队一个sudog(代表一个goroutine)

如果channel closed,解锁,直接返回panic

如果接收队列里面有goroutine,直接将要发送的数据拷贝到接收goroutine, return true

1.对于缓冲型的gorouting,如果还有缓冲空间 ,发送到缓冲区区
2.qp = buf的sendx位置
3.将数据从ep拷贝到qp
4.发送游标值加1 c.sendx++,如果发送游标值等于缓冲空间容量,游标值归零,c.sendx=0
5.缓冲区的数量加一 ,c.qcount++
6.解锁,return true

如果不需要阻塞,解锁,并直接返回false

channel满了,发送方会被阻塞,接下来会构造一个sudog

获取当前goroutine的指针,当前goroutine进入等待队列,当前goroutine被挂起

channel接收数据

chanrecv()函数

1.如果 channel == nil , 如果不阻塞 !block ,直接返回;否则 接收一个nil的channel ,goroutine挂起

  1. 非阻塞模式下 快速检测失败:
  • 非缓冲型: sendq里面没有goroutine在等待
  • 缓冲型:但buf里面没有元素

如果channel 被closed,return

3.加锁 lock(&c.lock)

  1. 读取数据:
    channel已关闭,且循环数组buf里没有元素 ,

nil channel :
closed panic
读: 阻塞
写:阻塞

closed channel:
close: panic
读:对应对象的零值
写:pinc

not nil,not closed channel :
close : 正常关闭
读: 阻塞或正常读取数据。缓冲型channel为空 or 非缓冲型channel无等待发送者goroutin
写:阻塞或正常写入。缓冲型channel buf 满了 or 非缓冲型channel无等待发送者channel

channle应用

停止信号:

CATALOG
  1. 1. 数据结构
  2. 2. 向channel发送数据
    1. 2.1. channel接收数据
    2. 2.2. channle应用