Channel

  • 1.简介
  • 2. channel的底层结构
  • 3.channel的基础用法
  • 4.Go的发送数据底层原理
  • 5.Go的接收数据底层原理
  • 6.Go中Channel的应用场景


1.简介

Go中有一句经典的名言:“不要通过共享内存的方式来通信,而要通过通信的方式来共享内存。”原因是因为:使用共享内存的方式会造成数据竞争,为了防止数据竞争所以需要加锁,共享内存的方式在高并发场景下的锁竞争激烈,开销大。Go语言的高并发特性依赖于goruntine和channel,采用channel进行通信可以控制并发的数量,可以使得生产者和消费者解耦,提高代码可读性

2. channel的底层结构

channel的底层结构是一个hchan的结构体

go语言定义通道中的消息类型 go语言channel 原理_Go


环形缓存:

go语言的channel采用的是环形缓存,环形缓存的内存空间可以复用,减少了GC的压力。channel的环形缓存由hchan的五个字段构成。

go语言定义通道中的消息类型 go语言channel 原理_缓存_02


qcout是环形缓存中已经保存数据的多少,dataqsize表示的是最多能缓存多少数据,buf是一个指向环形缓存第一个成员的指针,elemsize和elemtype是缓存数据的大小和类型。

两个队列

channel中含有两个队列分别是:接收队列发送队列

go语言定义通道中的消息类型 go语言channel 原理_Go_03


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的应用场景

  1. 解耦生产者和消费者,对协程通信进行解耦,生产者只需要往channel里面发送数据,消费者只需要从channel中获取数据。
  2. 控制并发的数量,可以通过channel来控制并发的规模
  3. 可以做任务的定时任务的超时处理,通常和select一起使用。