Channel理解 Golang

Channel概述

‌Channel在Go语言(Golang)中是一种用于在Goroutine之间进行通信和同步的机制‌。Channel提供了一种安全的方式,让多个Goroutine在同步或异步的情况下交换信息,而不需要使用复杂的锁机制。它的主要特点是类型安全——即Channel中只能传递同一种类型的数据‌。

实现

1、声明与创建‌:使用make函数创建一个Channel。例如,创建一个可以传递int类型的Channel:

ch := make(chan int)	//无缓冲区
ch := make(chan int ,3)	//有缓冲区,容量为3

2、‌发送数据‌:使用<-操作符向Channel发送数据。例如:

ch <- 10

3、接收数据:使用<-操作符从Channel接收数据。例如:

value := <-ch

有无缓冲区的区别(特性)

首先我们看一下channel的代码实现(go/src/runtime/chan.go):

type hchan struct {
    //channel分为无缓冲和有缓冲两种。
    //对于有缓冲的channel存储数据,借助的是如下循环数组的结构
    qcount   uint           // 循环数组中的元素数量
    dataqsiz uint           // 循环数组的长度
    buf      unsafe.Pointer // 指向底层循环数组的指针	//驿站货架
    elemsize uint16 		//能够收发元素的大小
    
    
    closed   uint32   //channel是否关闭的标志
    elemtype *_type   //channel中的元素类型
    
    //有缓冲channel内的缓冲数组会被作为一个“环型”来使用。
    //当下标超过数组容量后会回到第一个位置,所以需要有两个字段记录当前读和写的下标位置
    sendx    uint   // 已发送元素在循环数组中的索引	
    recvx    uint   // 已接收元素在循环数组中的索引
    
    //当循环数组中没有数据时,收到了接收请求,那么接收数据的变量地址将会写入读等待队列
    //当循环数组中数据已满时,收到了发送请求,那么发送数据的变量地址将写入写等待队列
    recvq    waitq  // 读等待队列		//取货的人排的队
    sendq    waitq  // 写等待队列		//进站的货物排的队
    
    
    lock mutex //互斥锁,保证读写channel时不存在并发竞争问题
}

可以看出hchan结构体的主要组成部分有四个:

  1. buf:用来保存goroutine之间传递数据的循环链表(实际数据结构是用数组实现的)。
  2. sendx和recvx:用来记录此循环链表当前发送或接收数据的下标值。
  3. sendq 和 recvq:用于保存向该chan发送和从改chan接收数据的goroutine的队列。
  4. lock:保证channel写入和读取数据时线程安全的锁。

其中无缓冲区的channel只用得上sendq、recvq、lock,有缓冲区的channel全部需要。

这时就可以看到有缓冲区和无缓冲区的channel的一些区别了:

  1. 有缓冲区的channel和无缓冲区的channel最大的区别就是有无缓冲区(狗头 ), 缓冲区的作用是记录当前channel中还存在的数据。当缓冲区充足的时候,channel的读写都不会阻塞(具体为当缓冲区不为0时,读操作不会阻塞;当缓冲区不为满时,写操作不会阻塞。)
  2. 有缓冲区的channel是异步的。无缓冲区的channel是同步的。可以理解为缓冲区是一个面包货架队列,当货架(缓冲区)没有装满时,厨师做好的面包(数据)会存放到货架,当货架满时,需要等到最前面面包被取走了,留出了空位再存放进货架。而无缓冲区的channel是一个尽职的面包外卖员,必须要将手上的面包交给某个人后,再拿着后续的面包给其他人。

根据面包理论(我编的 ,有错误欢迎指正)

某channel是一个面包店长,有缓冲区的channel有货架(channel),可以临时存放一些面包(数据)。同时会存在着很多的厨师,每个厨师(发送的channel)都会单独给他做面包(发送数据),而消费者(接收的channel)则会从面包店长那买面包(接收数据)。

没有缓冲区的面包店长就比较惨了,他没有货架。厨师(发送的channel)给他做好了一个面包,他就得充当外卖员,去把这个面包送到消费者(接收的channel)手上。

可以发现存在着一些问题,这些问题也是channel会引发错误的问题。

  1. 给一个 nil channel 发送数据,造成永远阻塞。货架或者外卖员存在,面包要给不存在的消费者送过去,即等待不存在的人来收货,就会一直等下去(阻塞)。
  2. 从一个 nil channel 接收数据,造成永远阻塞。店面的货架和外卖员都不存在,消费者要等待不存在的人来送货,也会一直等下去(阻塞)。
  3. 给一个已经关闭的 channel 发送数据,引起 panic。货架被撤了或者外卖员被裁了,厨师给他们新的面包,他们会很不乐意。所以会报错。
  4. 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值。面包店倒闭了,只会把库存的面包一件件卖完(返回数据和读取成功标识),然后告诉你我现在什么都没有了,跑路了(返回零值和读取失败标识)。外卖员被裁了,但因为他很有责任心,所以会把最后一件面包送到客户手上(返回数据和读取成功标识),然后再有人找他拿面包就会听到他说我被裁了(返回零值和读取失败标识)。

channel 是否线程安全?

是线程安全的,根据hchan的数据结构可以看出来,channel的使用是有互斥锁的。保证了读写操作的原子性。

channel读写流程

读取channel

依旧是面包理论(你现在是消费者,你想要去买个面包):

  1. 消费者(读取channel的channel)去买面包(数据),如果发现面包店排队了(recvq的长度大于零),那消费者需要老老实实跟在后面排队(进入recvq,然后休眠)。然后等店长叫你(唤醒)。
  2. 买面包的时候发现没有排队,并且货架上有面包,店长会把做的最早的面包给你(缓冲区头的数据)
  3. 买面包的时候发现没有排队,但是货架上没有面包了,你要成为排队的第一个人(进入recvq,然后休眠),等店长叫你。
  4. 拿到面包后,结束读取

换成实际的流程就是:

  1. 如果等待发送队列sendq不为空,且没有缓冲区,直接从sendq中取出G,把G中数据读出,最后把G唤醒,结束读取过程;
  2. 如果等待发送队列sendq不为空,此时说明缓冲区已满,从缓冲区中首部读出数据,把G中数据写入缓冲区尾部,把G唤醒,结束读取过程;
  3. 如果缓冲区中有数据,则从缓冲区取出数据,结束读取过程;
  4. 将当前goroutine加入recvq,进入睡眠,等待被写goroutine唤醒;

写入channel

面包理论(现在你是厨师辣,你现在刚做好了一个面包):

  1. 厨师(写入该channel的channel)做好了一个面包(准备发送数据),首先你看了一下有没有排队的人(recvq的长度大于0),如果有的话你把你做好的面包给店长,店长会叫醒排队的第一个人(唤醒等待的channel),然后把面包直接给他。
  2. 如果没有排队的人,你就会看看货架,如果货架没有满,你就把你的面包放到了货架的最后的面包的后面(存入缓冲区)
  3. 如果货架满了,你就需要去排厨师的队了(存入sendq,睡眠),等到店长提醒你货架有空位(唤醒)。

换成实际的流程就是:

  1. 如果等待接收队列recvq不为空,说明缓冲区中没有数据或者没有缓冲区,此时直接从recvq取出G,并把数据写入,最后把该G唤醒,结束发送过程;
  2. 如果缓冲区中有空余位置,将数据写入缓冲区,结束发送过程;
  3. 如果缓冲区中没有空余位置,将待发送数据写入G,将当前G加入sendq,进入睡眠,等待被读goroutine唤醒;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值