Channel
- 1.简介
- 2. channel的底层结构
- 3.channel的基础用法
- 4.Go的发送数据底层原理
- 5.Go的接收数据底层原理
- 6.Go中Channel的应用场景
1.简介
Go中有一句经典的名言:“不要通过共享内存的方式来通信,而要通过通信的方式来共享内存。”原因是因为:使用共享内存的方式会造成数据竞争,为了防止数据竞争所以需要加锁,共享内存的方式在高并发场景下的锁竞争激烈,开销大。Go语言的高并发特性依赖于goruntine和channel,采用channel进行通信可以控制并发的数量,可以使得生产者和消费者解耦,提高代码可读性。
2. channel的底层结构
channel的底层结构是一个hchan的结构体。
环形缓存:
go语言的channel采用的是环形缓存,环形缓存的内存空间可以复用,减少了GC的压力。channel的环形缓存由hchan的五个字段构成。
qcout是环形缓存中已经保存数据的多少,dataqsize表示的是最多能缓存多少数据,buf是一个指向环形缓存第一个成员的指针,elemsize和elemtype是缓存数据的大小和类型。
两个队列
channel中含有两个队列分别是:接收队列和发送队列。
close状态值
hchan结构体中close的状态值,1表示channel关闭,0表示channel打开
互斥锁:该互斥锁保证同一个时刻只有一个协程可以操作channel
3.channel的基础用法
func main() {
// channel的创建
ch := make(chan int, 2) // 创建一个int类型的含有两个缓存的channel
ch2 := make(chan int) // 创建一个int类型的不含有缓存的channel
// 往channel中添加元素
i := 10
ch <- i // 将i加入到ch的管道中,由于存在缓存所以不会阻塞
// channel中输出元素
x := <-ch
fmt.Println(x)
// 没有缓存的channel在往里面添加元素或者接受元素的时候会发生阻塞
go func() {
t := <-ch2 // 会发生阻塞,ch2没有缓存
fmt.Println("阻塞结束,输出", t)
}()
ch2 <- 1 //
}
4.Go的发送数据底层原理
(1)直接发送:在发送数据之前,已经有协程在休眠等待,给channel发送数据不需要放在缓存中,把数据拷贝给一个协程,然后唤醒一个协程。
**(2)放入缓存:**没有协程在等待接收,channel是存在缓存的,将数据放在缓存区中,维护索引。
**(3)休眠等待:**没有等待接收的协程,channel没有缓存区,或者缓存区已经被填满了。协程包装成sudog并且加入到发送队列中,休眠等待。
5.Go的接收数据底层原理
(1)有发送等待的协程,并且没有缓存或者缓存空,从发送等待的协程中接收:先将数据拷贝过来,然后唤醒协程。
(2)有等待的发送的协程,从缓存接收:有等待发送的协程,并且有缓存,从缓存取走数据,将休眠协程的数据放到缓存中,并且唤醒休眠协程。
(3)接收缓存:没有协程在等待发送,从缓存中取走一个数据
(4)阻塞接收:没有协程在等待发送,并且缓存区中没有数据或者没有缓存,则直接阻塞。
6.Go中Channel的应用场景
- 解耦生产者和消费者,对协程通信进行解耦,生产者只需要往channel里面发送数据,消费者只需要从channel中获取数据。
- 控制并发的数量,可以通过channel来控制并发的规模
- 可以做任务的定时和任务的超时处理,通常和select一起使用。