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

关闭已经被关闭的管道

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

相关推荐
Leweslyh14 小时前
【实战】如何在家定位国际空间站 (ISS)? —— 坐标转换的魔法 (例题 5.9)
开发语言·javascript·ecmascript
Sheep Shaun14 小时前
深入理解AVL树:从概念到完整C++实现详解
服务器·开发语言·数据结构·c++·后端·算法
_leoatliang14 小时前
基于Python的深度学习以及常用环境测试案例
linux·开发语言·人工智能·python·深度学习·算法·ubuntu
少控科技14 小时前
QT新手日记025 - W002程序代码
开发语言·qt
a程序小傲14 小时前
Maven 4 要来了:15 年后,Java 构建工具迎来“彻底重构”
java·开发语言·spring boot·后端·spring·重构·maven
云深麋鹿14 小时前
二.顺序表和链表
c语言·开发语言·数据结构·链表
XH华14 小时前
备战蓝桥杯,第二章:C++语言的输入输出(上)
开发语言·c++·蓝桥杯
C++ 老炮儿的技术栈14 小时前
Qt中自定义 QmyBattery 电池组件开发
c语言·开发语言·c++·windows·qt·idea·visual studio
Dxy123931021614 小时前
Python的PIL如何转Base64字符串:完整指南
开发语言·python
麦聪聊数据14 小时前
金融级数据库运维的“零信任”实践:如何在合规与效率间寻找平衡点?
运维·数据库·后端·sql·金融