简单易懂,解析Go语言中的Channel管道
创作时间:
作者:
@小白创作中心
简单易懂,解析Go语言中的Channel管道
引用
CSDN
1.
https://blog.csdn.net/lonely__snow/article/details/145735224
在Go语言中,Channel是一种用于在goroutine之间传递数据的机制。它不仅提供了线程安全的数据传输方式,还支持阻塞和非阻塞的读写操作。本文将从Channel的初始化、读写操作以及底层实现原理等多个维度,深入解析这一重要的并发控制机制。
Channel 管道
1. 初始化
Channel可以通过var
声明为nil管道,也可以使用make
函数进行初始化。len()
函数用于获取缓冲区中元素的数量,而cap()
函数则返回缓冲区的大小。
// 变量声明
var a chan int
// 使用make初始化
b := make(chan int) // 不带缓冲区
c := make(chan string,2) // 带缓冲区
ch1 := make(chan int) // 0 0
ch2 := make(chan int, 2)// 1 2
ch2 <- 1
fmt.Println(len(ch1), len(ch2), cap(ch1), cap(ch2))
2. 读写操作
使用<-
操作符来表示数据流向。当缓冲区满时写入或缓冲区空时读取都会阻塞,直到被其他goroutine唤醒。
a := make(chan int, 3)
a <- 1 // 数据写入管道
<-a // 管道读出数据
Channel默认是双向可读写的,但也可以在创建函数时指定为单向读或单向写。
func write(ch chan<- int,a int) {
ch <- a
// <- ch 无效运算: <- ch (从仅发送类型 chan<- int 接收)
}
func read(ch <-chan int) {
<- ch
//ch <- 1 无效运算: ch <- 1 (发送到仅接收类型 <-chan int)
}
读写值为nil的管道会导致永久阻塞,触发死锁。
var ch chan int
ch <- 1 // fatal error: all goroutines are asleep - deadlock!
<-ch // fatal error: all goroutines are asleep - deadlock!
读写已关闭的管道:有缓冲区时可以成功读取缓冲区内容,无缓冲区时读取零值并返回false;向已关闭的管道写入数据会触发panic。
ch1 := make(chan int)
ch2 := make(chan int, 2)
go func() {
ch1 <- 1
}()
ch2 <- 2
close(ch1)
close(ch2)
v1, b1 := <-ch1 //0 false
v2, b2 := <-ch2 //2 true
println(v1, v2, b1, b2)
ch1 <- 1 //panic: send on closed channel
ch2 <- 1 //panic: send on closed channel
3. 实现原理
Channel底层通过环形队列实现其缓冲区功能,同时使用两个等待队列存储被阻塞的goroutine,并通过互斥锁保证并发安全。
type hchan struct {
qcount uint // 队列中数据的总数
dataqsiz uint // 环形队列的大小
buf unsafe.Pointer // 指向底层的环形队列
elemsize uint16 // 元素的大小(以字节为单位)
closed uint32 // 表示通道是否已关闭
elemtype *_type // 元素的类型(指向类型信息的指针)
sendx uint // 写入元素的位置
recvx uint // 读取元素的位置
recvq waitq // 等待接收的队列(包含等待接收的 goroutine)
sendq waitq // 等待发送的队列(包含等待发送的 goroutine)
// lock 保护 hchan 中的所有字段,以及阻塞在这个通道上的 sudogs 中的几个字段。
// 在持有此锁时,不要更改另一个 G 的状态(特别是不要使 G 变为可运行状态),
// 因为这可能会与栈收缩操作发生死锁。
lock mutex //互斥锁
}
环形队列通过数组实现,使用sendx和recvx两个指针分别指向写入和读取位置。等待队列遵循先进先出原则,阻塞中的goroutine会被相反的操作依次唤醒。
如果写入时等待接收队列非空(recvq),那么直接将数据给到等待的goroutine,无需经过缓冲区。
select
语句可以监控单个或多个管道内是否有数据,有数据时将其读出;如果没有数据也不会阻塞,直接返回。需要注意的是,select
的执行顺序是随机的。
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go write(ch1)
go write(ch2)
for {
select {
case e := <-ch1:
fmt.Printf("ch1:%d\n", e)
case e := <-ch2:
fmt.Printf("ch2:%d\n", e)
default:
fmt.Println("none")
time.Sleep(1 * time.Second)
}
}
}
func write(ch chan<- int) {
for {
ch <- 1
time.Sleep(time.Second)
}
}
使用for-range
循环读取管道时,管道关闭后不会继续读取管道内的数据;而使用普通for
循环读取管道时,管道关闭后仍会继续读取管道内的数据,返回一堆零值和false。
func main() {
ch1 := make(chan int)
go write(ch1)
//for e := range ch1 { // 关闭后不会再从管道读取数据
// fmt.Print(e)
//}
//1111
for { // 关闭后仍在从管道读取数据。返回 零值,false
fmt.Print(<-ch1)
}
//11110000000000000000000000000000000000000.....
}
func write(ch chan<- int) {
for i := 1; i < 5; i++ {
ch <- 1
time.Sleep(time.Second)
}
close(ch)
}
热门推荐
六旬老人讲述:农村“妻荒难娶”背后,存在着更大的隐患和危机
河北农民家中藏明朝圣旨,传承19代拒绝上交,专家:绢帛不易保存需防潮防蛀
iOS 18将支持广告屏蔽功能,引媒体行业不满
松针油的功效与作用及食用方法
离婚财产分割的法律说明与实务操作指南
探访!高铁盒饭是怎样“诞生”的?
蔡磊妻子:攻克渐冻症需要更多“蔡磊”
医药GMP净化车间建设与管理指南
适应新发展,公共管理学二级学科边界拓展
如何在春天种植郁金香:技巧与注意事项详解
排量削减至850cc,MotoGP四冲时代第四纪元会更精彩吗?
“玻璃人”坚强起来的理由|世界血友病日
古代“连弩”:机械巧思铸就的战场利器
中药过期能吃吗?过期中药的危害与处理方法
如何给孩子补钙?这可是个技术活儿!
5种酸的广泛用途:日常生活与你息息相关的化学元素
桌面显卡性能天梯图:如何解读与选择适合自己的显卡?
牛肉羹的营养价值及健康影响分析
报告显示中国近七成孩子在6岁前开始阅读
医保改革为肺癌治疗带来新希望,12种新药纳入报销目录
过敏性荨麻疹的治疗方法
美“ReSCIND”探索项目启动:以“决策扰乱与认知干预”原理狩猎网络空间攻击者
自制无人机如何合法飞行?一文详解飞行条件与著作权侵权处理
生活中的药食同源,吃出健康生活之白芷
雪是冷的,山是热的:长白山“冰雪走廊”火起来
18个词汇覆盖所有啤酒种类
中医诊所开办全流程:从选址到开业,手把手教您!
《黑色四叶草剧场版》四位新登场魔法帝:从英雄到反叛者的宿命
2024年成都高中中考录取分数52强分析
学会“延迟满足”,会让一个人变得越来越优秀?