05.channel
# 01.channel的整体结构图
# 1、channel结构图
- channel本质是一个
hchan
这个结构体
type hchan struct {
qcount uint // 队列中的元素个数
dataqsiz uint // 环形队列的大小,0 表示无缓冲通道
buf unsafe.Pointer // 环形队列的指针,用于存储元素
elemsize uint16 // 单个元素的大小
closed uint32 // 表示通道是否关闭,1 表示关闭
sendx uint // 发送操作的下标(对于缓冲通道)
recvx uint // 接收操作的下标(对于缓冲通道)
recvq waitq // 等待接收的 goroutine 队列
sendq waitq // 等待发送的 goroutine 队列
lock mutex // 互斥锁,保证并发安全
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
- 简单说明:
buf
是缓冲区,所有数据存储在这个环形缓冲区中
(环形队列)sendx
和recvx
是指向缓冲区中发送和接收数据位置的指针(管理读写顺序)sendx指针
:指向下一个可以写入数据的位置recvx指针
:指向下一个可以读取数据的位置
recvq
和sendq
是两个用于存储阻塞 goroutine 的队列
(不是用于存储数据的队列)- 当缓冲区已满时,无法继续发送,发送 goroutine 会进入
sendq
等待 - 当缓冲区为空时,无法接收数据,接收 goroutine 会进入
recvq
等待 dataqsiz
:通道缓冲区的大小,值为 0 表示无缓冲通道
- 当缓冲区已满时,无法继续发送,发送 goroutine 会进入
lock
是个互斥锁
,发送或接收前都需要加锁
先进先出
- 先从 Channel 读取数据的 Goroutine 会先接收到数据
- 先向 Channel 发送数据的 Goroutine 会得到先发送数据的权利
# 2、创建channel
- 创建channel实际上就是在内存中实例化了一个
hchan
的结构体,并返回一个ch指针 - 我们使用过程中channel在函数之间的传递都是用的这个指针
- 这就是为什么函数传递中无需使用channel的指针,而直接用channel就行了,因为channel本身就是一个指针
# 3、channel中队列如何实现
channel中有个缓存buf,是用来缓存数据的(假如实例化了带缓存的channel的话)队列。
当使用
send (ch <- xx)
或者recv ( <-ch)
的时候,首先要锁住hchan
这个结构体然后开始
send (ch <- xx)
数据,这时候满了,队列塞不进去了然后是取
recv ( <-ch)
的过程,是个逆向的操作,也是需要加锁
# 4、channel缓存满发生什么
使用的时候,我们都知道,当channel缓存满了,或者没有缓存的时候
我们继续send(ch <- xxx)或者recv(<- ch)会阻塞当前goroutine,但是,是如何实现的呢?
Go的goroutine是用户态的线程(
user-space threads
),用户态的线程是需要自己去调度的Go有运行时的scheduler去帮我们完成调度这件事情
# 02.chan读写问题
# 1、对关闭chan读写
- 读关闭chan
- 可以正确读取到,如果无元素,返回零值,第二个
bool
值一直为false
- 可以正确读取到,如果无元素,返回零值,第二个
- 写关闭chan:
chan
会panic
# 2、未初始化chan读写
- 读取未初始化的通道(
nil
chan)- 程序将永远阻塞在这个读取操作上,因为
nil
通道没有能力传输数据
- 程序将永远阻塞在这个读取操作上,因为
var ch chan int
x := <-ch // 试图从未初始化的通道读取
1
2
2
写入未初始化的通道(
nil
chan)- 程序同样会永远阻塞在这个写入操作
select
语句中操作未初始化的通道
var ch chan int
select {
case x := <-ch:
// 永远不会执行
case ch <- 1:
// 永远不会执行
default:
// 如果有 default 分支,它会被执行
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
上次更新: 2024/10/15 16:27:13