channel

  • 概述

在源码中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
type hchan struct {
// chan 里元素数量
qcount uint
// chan 底层循环数组的长度
dataqsiz uint
// 指向底层循环数组的指针
// 只针对有缓冲的 channel
buf unsafe.Pointer
// chan 中元素大小
elemsize uint16
// chan 是否被关闭的标志
closed uint32
// chan 中元素类型
elemtype *_type // element type
// 已发送元素在循环数组中的索引
sendx uint // send index
// 已接收元素在循环数组中的索引
recvx uint // receive index
// 等待接收的 goroutine 队列
recvq waitq // list of recv waiters
// 等待发送的 goroutine 队列
sendq waitq // list of send waiters

// 保护 hchan 中所有字段 保证原子性
lock mutex
}

创建一个容量为 6 的,元素为 int 型的 channel 数据结构如下 :

  • 写入数据

  • 接收数据

  • 注意事项

必须值得注意的是:

关闭 channel 后,对于等待接收者而言,会收到一个相应类型的零值

优雅关闭channel, 根据 sender 和 receiver 的个数,分下面几种情况:

  1. 一个 sender, M 个 receiver, 直接在sender端关闭,用双值语句检查是否关闭
  2. N 个 sender,一个 reciver,提供一个关闭信号channel,reciver端发送信号(关闭信号channel)
  3. N 个 sender, M 个 receiver,提供一个中间channel,一个信号channel,reciver端先发信号给中间channel,由中间channel发送信号关闭信号channel,实现return
操作 nil channel closed channel not nil, not closed channel
close panic panic 正常关闭
读 <- ch 阻塞 读到对应类型的零值 阻塞或正常读取数据。缓冲型 channel 为空或非缓冲型 channel 没有等待发送者时会阻塞
写 ch <- 阻塞 panic 阻塞或正常写入数据。非缓冲型 channel 没有等待接收者或缓冲型 channel buf 满时会被阻塞
  • 常见应用

  • 协程池

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
func main() {
taskCh := make(chan int, 100)
go worker(taskCh)

// 塞任务
for i := 0; i < 10; i++ {
taskCh <- i
}

// 等待 1 小时
select {
case <-time.After(time.Hour):
}
}

func worker(taskCh <-chan int) {
const N = 5
// 启动 5 个工作协程
for i := 0; i < N; i++ {
go func(id int) {
for {
task := <- taskCh
fmt.Printf("finish task: %d by worker %d\n", task, id)
time.Sleep(time.Second)
}
}(i)
}
}
  • 限流

1
2
3
4
5
6
7
8
9
10
11
12
13
var limit = make(chan int, 3)

func main() {
// …………
for _, w := range work {
go func() {
limit <- 1
w()
<-limit
}()
}
// …………
}
  • ref

go-questions: channel


channel
https://fatwang1.github.io/2024/12/20/2024121901/
作者
衣云乘风
发布于
2024年12月19日
许可协议