上篇文章讲了 channel
的基本使用,讲了一些使用时需要注意的事项,本文将重点介绍 channel
中的两个数据结构:循环队列 与 双端链表 。
channel 的需求描述
为了理解这些数据结构解决了什么问题,我们先来做个简单的回顾,看看为什么需要这两个数据结构,他们解决了什么问题。我们知道 goroutine 是用户态的线程,不同的 goroutine 之间是有消息传递这个需求的。在原始的进程与线程(系统线程)编程中我们会采用管道的方式,而 channel
就是用户态线程传递消息的管道实现,并且是类型安全的。
既然 channel
是一个管道,用来满足不同 goroutine 间交换消息的。那么实现这样一个管道要怎么做呢?
来看看我们日常传递消息的需求:
能够有多个 goroutine 向
channel
进行读写,并且保证没有竞争问题,需要用 队列 来管理阻塞的 goroutine,解决竞争问题;需要管理发送到
channel
的消息(有缓冲、无缓冲),对于有缓存的channel
可以采用 循环队列 来管理多个消息。
当然上面的需求是经过简化的,比如 channel
还需要具备阻塞、唤醒 goroutine 的能力,不过为了本文我们更加专注焦点问题,先只关注上面两个问题。
channel 的数据结构
接下来我们分析一下 channel
在实际运行中,它的结构体是怎么样的。当然这又分为两种类型,有缓冲与无缓冲的。我们先来看一个无缓冲的情况。
无缓冲
先把示例代码贴出来。就是两个读的 goroutine 被阻塞在一个无缓冲的 channel 上。
func main() {
ch := make(chan int) // 无缓冲
go goRoutineA(ch)
go goRoutineB(ch)
ch <- 1
time.Sleep(time.Second * 1)
}
func goRoutineA(ch chan int) {
v := <-ch
fmt.Printf("A received data: %d\n", v)
}
func goRoutineB(ch chan int) {
v := <-ch
fmt.Printf("B received data: %d\n", v)
}