Golang-channel实现
一.简介
如果说goroutine是Go语言程序的并发体的话,那么channels则是它们之间的通信机制。一个channel是一个通信系统,它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有一个特定的类型,也就是channels可发送数据的类型。
二.使用
2.1 创建channel
ch := make(chan int)
2.2 channel读写
ch := make(chan int)// write to channel
ch <- x// read from channel
x <- ch// another way to read
x = <- ch
channel 一定要初始化后才能进行读写操作,否则会永久阻塞。
2.3 关闭 channel
ch := make(chan int)close(ch)
有关 channel 的关闭,你需要注意以下事项:
- 关闭一个未初始化(nil) 的 channel 会产生 panic
- 重复关闭同一个 channel 会产生 panic
- 向一个已关闭的 channel 中发送消息会产生 panic
- 从已关闭的 channel 读取消息不会产生 panic,且能读出 channel 中还未被读取的消息,若消息均已读出,则会读到类型的零值。从一个已关闭的 channel 中读取消息永远不会阻塞,并且会返回一个为 false 的 ok-idiom,可以用它来判断 channel 是否关闭
- 关闭 channel 会产生一个广播机制,所有向 channel 读取消息的 goroutine 都会收到消息
三.channel的两种类型
3.1 有缓冲channel
ch := make(chan int, 10)
有缓存的 channel 类似一个阻塞队列(采用环形数组实现)。当缓存未满时,向 channel 中发送消息时不会阻塞,当缓存满时,发送操作将被阻塞,直到有其他 goroutine 从中读取消息;相应的,当 channel 中消息不为空时,读取消息不会出现阻塞,当 channel 为空时,读取操作会造成阻塞,直到有 goroutine 向 channel 中写入消息。
3.2 无缓冲channel
ch := make(chan int)
从无缓存的 channel 中读取消息会阻塞,直到有 goroutine 向该 channel 中发送消息;同理,向无缓存的 channel 中发送消息也会阻塞,直到有 goroutine 从 channel 中读取消息。
3.3 select 使用
select 用法类似 IO 多路复用,可以同时监听多个 channel 的消息状态,看下面的例子
select {case <- ch1:...case <- ch2:...case ch3 <- 10;...default:...
}
msgCh := make(chan struct{})
quitCh := make(chan struct{})
for {select {case <- msgCh:doWork()case <- quitCh:finish()return
}
- select 可以同时监听多个 channel 的写入或读取
- 执行 select 时,若只有一个 case 通过(不阻塞),则执行这个 case 块
- 若有多个 case 通过,则随机挑选一个 case 执行
- 若所有 case 均阻塞,且定义了 default 模块,则执行 default 模块。若未定义 default 模块,则 select 语句阻塞,直到有 case 被唤醒。
- 使用 break 会跳出 select 块。
三.底层实现
2.1 hchan结构
type hchan struct {qcount uint // 队列中当前数据的个数dataqsiz uint // size of the circular queuebuf unsafe.Pointer // 数据缓冲区,存放数据的环形数组elemsize uint16 // channel中数据类型的大小(单个元素的大小)closed uint32 // 表示channel是否关闭标识位elemtype *_type // 队列中的元素类型sendx uint // 当前发送元素的索引recvx uint // 当前接收元素的索引recvq waitq // 接受等待队列,由recv行为(也就是<-ch)阻塞在channel上的goroutine队列sendq waitq // 发送等待队列, 由send行为(也就是ch<-)阻塞在channel上的goroutine队列//lock保护chann中的所有字段,以及在此通道上阻塞的sudoG中的几个字段。//保持此锁时不要更改另一个G状态(特别是没准备好G),因为这可能会因堆栈收缩而死锁lock mutex
}//发送及接收队列的·1结构体
type waitq struct {first *sudoglast *sudog
}
- qcount uint // 当前队列中剩余元素个数。
- dataqsiz uint // 环形队列长度,即缓冲区的大小,即make(chan T,N),N。
- buf unsafe.Pointer // 环形队列指针。
- elemsize uint16 // 每个元素的大小。
- closed uint32 // 表示当前通道是否处于关闭状态。创建通道后,该字段设置为0,即通道打开; 通过调用close将其设置为1,通道关闭。
- elemtype *_type // 元素类型,用于数据传递过程中的赋值。
- sendx uint 和 recvx uint是环形缓冲区的状态字段,它指示缓冲区的当前索引 - 支持数组,它可以从中发送数据和接收数据。
- recvq waitq // 等待读消息的goroutine队列。
- sendq waitq // 等待写消息的goroutine队列。
- lock mutex // 互斥锁,为每个读写操作锁定通道,因为发送和接收必须是互斥操作。
2.2 创建过程
2.2.1 写入操作
1.创建带buffer的channel
2.向channel中写入数据
3.3 写入过程如下:
- 锁定整个管道结构。
- 确定写入,尝试从等会带队列等待goroutine,然后将元素直接写入goroutine。
- 如果recvq为空,则确定缓冲区是否可用。如果可用,从当前goroutine复制数据到缓冲区。
- 如果缓冲区已满,则要写入的元素将保存在当前正在执行的goroutine结构中,并且当前goroutine将在sendq中排队并从运行中挂起。
- 写入完成释放锁。
2.2.2 读取过程
- 先读取channel全局锁。
- 尝试sendq从等待队列中获取等待的goroutine。
- 如果有等待的goroutine,且有缓冲区(缓冲区已满),从缓冲区队首取出数据,再从sendq取出一个goroutine。将goroutine中数据存入buf对位,结束读取释放锁。
- 如没有后等待的goroutine,且缓冲区有数据,直接读取缓冲区数据,解释读取释放锁。
- 如果没有等待的goroutine,且没有缓冲或缓冲区域为空,将当前的goroutine加入denq排队,进入睡眠,等待被写goroutine唤醒。结束释放锁。
Golang-channel实现相关推荐
- Golang channel 快速入门
文章目录 1.简介 2.缓冲 channel 3.range 和 close 操作 4.select 操作 5.注意要点 6.常见用法 参考文献 1.简介 channel 提供了一种通信机制,通过发送 ...
- golang channel 管道 通道 信道 使用总结
不同于传统的多线程并发模型使用共享内存来实现线程间通信的方式,golang 的哲学是通过 channel 进行协程(goroutine)之间的通信来实现数据共享: > Do not commun ...
- golang channel本质——共享内存
channel是golang中很重要的概念,配合goroutine是golang能够方便实现并发编程的关键.channel其实就是传统语言的阻塞消息队列,可以用来做不同goroutine之间的消息传递 ...
- Golang channel 源码分析
以下源码都摘自 golang 1.16.15 版本. 1. channel 底层结构 Golang 中的 channel 对应的底层结构为 hchan 结构体(channel的源码位置在Golang包 ...
- golang channel 管道 有无缓存的区别
无缓冲的与有缓冲channel有着重大差别,那就是一个是同步的 一个是非同步的. 比如 c1:=make(chan int) 无缓冲 c2:=make(chan int,1) ...
- golang channel 管道
channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication). 它的操作符是箭头 <- . ch <- v ...
- golang channel的一些总结
这是一个死锁的例子 package mainimport ("fmt""sync" )func main() {var (ch chan intchInt01, ...
- golang channel的一点说明
1.无缓存channel的就是同步.有缓存channel就是异步 2.channel最好还是关闭掉,不要等到gc回收 3.channel无缓存的时候,最好先准备读,然后另一个协程发起写,如果同步cha ...
- golang——channel笔记
1.for i := range channel { //... } 相当于 循环进行 i<-channel,直至close(channel) 2. · 给一个 nil channel 发送数据 ...
- golang——channel
目录 1.分类 channel的三种状态 channel的两种类型--有缓冲,无缓冲 无缓冲 有缓冲 2.操作 1.创建 2.发送 3.接收 4.关闭 3.使用场景 4.channel底层 5.cha ...
最新文章
- [智力考题]比尔盖茨只有3分的考题
- JSON Path表达式
- SVM-支持向量机原理详解与实践之四
- C语言中的EOF符号常量
- Python中json模块的使用,以及json.loads()和json.dumps()的区别
- 文件的上传和下载---学习笔记
- php xlsx格式转换csv,我想使用C将.xls或.xlsx文件转换为.csv格式#
- 1分钟学会python_快速入门:十分钟学会Python
- CSU 1115 最短的名字
- malloc开辟的空间在哪一个区间_专业指南 | 室内设计和空间设计区别,到底该选哪一个?...
- python psutil模块怎么在线下载_python之psutil模块
- 如何申请CSDN博客?
- 一个意外错误使你无法删除该文件,文件或目录损坏且无法读取
- 国产系统为什么用linux,为什么国产操作系统不用Unix,而是集体用Linux
- 网络口碑营销从“可以做”、“值得做”到了“必须做好”的阶段!
- 阿拉伯数字改为汉字的大写
- 基于微信小程序的音乐播放器设计
- 30系 显卡显存被占用又找不到进程的解决办法
- Power law and Power law distribution(幂律和幂律分布)
- 基于搜狗微信的爬虫知识总结