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的管道

关闭已经被关闭的管道

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

相关推荐
零零壹113 分钟前
使用 Node.js、Express 和 React 构建强大的 API
javascript·后端·github
楽码10 分钟前
检查go语言变量内存结构
后端·go·计算机组成原理
凯哥197016 分钟前
Socket.IO 分布式系统优化指南
redis·后端
帮帮志17 分钟前
PyCharm 开发工具 修改背景颜色
开发语言·python·青少年编程
异常君21 分钟前
深入剖析 Java ReentrantLock:解锁显式锁的高级特性与实战应用
java·后端
一个天蝎座 白勺 程序猿21 分钟前
Python(14)Python内置函数完全指南:从基础使用到高阶技巧
开发语言·python
张立龙66624 分钟前
单链表各种操作实现(数据结构C语言多文件编写)
c语言·开发语言·数据结构
写bug写bug28 分钟前
彻底搞懂如何通过 ZooKeeper 实现注册中心
java·后端·zookeeper
追逐时光者36 分钟前
Visual Studio 2022 v17.13新版发布:强化稳定性和安全,助力开发提效!
后端·.net·visual studio
火山上的企鹅37 分钟前
异形遮罩之QML中的 `OpacityMask` 实战
开发语言·qml·opacitymask