go原理刨析之channel

提起go里边的管道无人不知无人不晓,go原理中推荐的就是通过管道来通信,而不是通过共享内存来通信。

我们先创建一个channel

channel的初始化有两种:

复制代码
// 这种方式只声明不初始化
var ch chan int 
// 使用make 
ch1 := make(chan string) // 无缓冲的channel
ch2 := make(chan string,5) // 有缓冲的channel

此外还有channel 特有的操作符

复制代码
ch := make(chan int,10)
ch <- 1  // 数据流入管道
for _,v := range ch {
	fmt.Println(v)// 数据流出管道
}

一般使用内置函数close 关闭 channel

内置函数len cap 分别查询缓冲区中数据的个数以及缓冲区的大小

当管道是无缓冲区的管道的时候,从管道中读取和写入都会阻塞,知道有写成从管道写入或读取数据

管道有缓冲区但缓冲区无数据的时候读取也会阻塞,直到有协程写入数据。类似向管道写入数据如果缓冲区已满的话也是无法写入的

对于值为nil的管道,无论读写都会阻塞,永久阻塞

关闭已经关闭的管道或者是向已经关闭的管道中进行写入会触发Panic

管道读取的时候类似map 最多可以有两个返回值,第一个表示读出的数据,第二个表示是否成功读取了数据

管道事一种先入先出的队列 FIFO 数据总是按照写入的顺序流出管道

协程读取管道时,阻塞的条件有:

  • 管道无缓冲区
  • 管道缓冲区无数据
  • 管道初始化为nil,但并未分配内存

协程写入管道,阻塞的条件有:

  • 管道无缓冲区
  • 管道缓冲区已满
  • 管道初始化为nil,但并未分配内存
管道内部结构以及底层原理

channel 的源码在src/runtime/chan.go:chan

复制代码
type hchan struct {
	qcount   uint           // 当前队列中剩余的元素
	dataqsiz uint           // 环形队列的长度,即可以存放的元素个数
	buf      unsafe.Pointer // points to an array of dataqsiz elements 指向内存的指针
	elemsize uint16  // 每个元素的大小
	synctest bool // true if created in a synctest bubble 
	closed   uint32  // 标识关闭
	timer    *timer // timer feeding this chan
	elemtype *_type // element type 元素类型
	sendx    uint   // send index 写入队列下标 指示元素写入时存放到队列的位置
	recvx    uint   // receive index  指示下一个被读取的元素在队列中的位置
	recvq    waitq  // list of recv waiters 等待读取消息的协程队列  
	sendq    waitq  // list of send waiters 等待写消息的协程队列。

	// lock protects all fields in hchan, as well as several
	// fields in sudogs blocked on this channel.
	//
	// Do not change another G's status while holding this lock
	// (in particular, do not ready a G), as this can deadlock
	// with stack shrinking.
	lock mutex  // 互斥锁 chan不允许并发读写
}

buf 指针指向的是一个环形队列做为缓冲区

使用数组来实现这个队列

sendx,recvx分别表示队尾和对首

dataqsiz指示队列长度为6,即可以缓冲6个元素

qcount 表示目前队列中还有两个元素

等待队列

从管道读取数据时,如果管道缓冲区为空或者没有缓冲区,当前协程会被阻塞,并加入recvq队列

向管道写入数据,如果缓冲区已满或者没有缓冲区,则当前协程会被阻塞并加入sendq队列

处于等待队列的协程会在其他协程操作管道时被唤醒:

  • 因读阻塞的协程会被向管道写入数据的协程唤醒
  • 因写阻塞的协程会被从管道读取数据的协程唤醒
    一般情况下recvq snedq 一般至少一个为空,只有一个例外,那就是同一个协程使用select语句向管道一边写入数据。一边读取数据,此时协程会分别位于两个等待队列中
创建 写数据 读取数据 时内部流程

创建管道:

就是初始化hchan结构体

缓存区长度由内置函数make设置

buf的大小由元素大小和缓冲区长度共同决定

向管道中写入数据:

  • 如果缓冲区有空余位置,则将数据写入缓冲区,结束发送过程
  • 如果缓冲区没有空余位置,将协程加入sendq队列,等到读协程唤醒

-当recvq不为空时说明有一个协程在等待接收数据 很明显此时缓冲区为空,那么写协程进入后不用写入缓冲区,直接传递给recvq中的第一个

从管道读数据:

  • 如果缓冲区有数据,直接读出
  • 如果缓冲区没有数九,将协程加入recvq队列,进入睡眠并等待被写协程唤醒
    同上在sendq不为空的情况下,且没有缓冲区,直接获取数据,不用写入缓冲区

关闭管道时:

关闭管道时会把recvq中的协程全部唤醒,这些协程获取的数据都为对应类型的灵芝,sendq的协程也会唤醒,但是相当于向关闭的channel ,里边写数据有可能触发Panic

会触发panic的操作:

关闭值为nil的管道

关闭已经被关闭的管道

向已经关闭的管道写入数据

相关推荐
SaleCoder40 分钟前
用Python构建机器学习模型预测股票趋势:从数据到部署的实战指南
开发语言·python·机器学习·python股票预测·lstm股票模型·机器学习股票趋势
熟悉的新风景6 小时前
springboot项目或其他项目使用@Test测试项目接口配置-spring-boot-starter-test
java·spring boot·后端
玩代码6 小时前
备忘录设计模式
java·开发语言·设计模式·备忘录设计模式
技术猿188702783517 小时前
实现“micro 关键字搜索全覆盖商品”并通过 API 接口提供实时数据(一个方法)
开发语言·网络·python·深度学习·测试工具
放飞自我的Coder7 小时前
【colab 使用uv创建一个新的python版本运行】
开发语言·python·uv
晴空月明7 小时前
分布式系统高可用性设计 - 监控与日志系统
后端
艾莉丝努力练剑8 小时前
【数据结构与算法】数据结构初阶:详解顺序表和链表(四)——单链表(下)
c语言·开发语言·数据结构·学习·算法·链表
zyhomepage8 小时前
科技的成就(六十九)
开发语言·网络·人工智能·科技·内容运营
珊瑚里的鱼8 小时前
第十三讲 | map和set的使用
开发语言·c++·笔记·visualstudio·visual studio
逑之8 小时前
C++笔记1:命名空间,缺省参数,引用等
开发语言·c++·笔记