简单易懂,解析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)
}
热门推荐
菏泽牡丹和洛阳牡丹对比,谁更胜一筹?
英语单词记忆有妙招:四种实用方法助你轻松掌握
出现这些症状,可能是突发脑中风!正确做出判断,立即就医!
美媒评NBA史上影响力最大的20位球星:库里第五,詹姆斯第二!
全面分析2025年红辣椒种子市场
如何写C语言编程题答案
只有遇到"正缘",你才会出现这4种感觉
团队竞技如何使用开镜:提高精度、增加战术选择、适应不同武器、提升反应速度
电脑散热器不转怎么办?15种原因及解决方法全解析
两台电脑之间如何进行数据传输?简单操作实现电脑数据共享
仿制药和原研药该咋选?这4个仿制药便宜好用,4类药优先用原研药
小学校园文化建设思考:如何构建有意义的非正式学习空间
只有你不再恐惧害怕的时候,你才算是自己真正的主人
暂停产能置换背后,凸显钢铁行业转型难题
个人开设外币储蓄账户全攻略:是时候说再见给汇率波动了!
S36赛季中路霸主:甄姬的强势崛起与策略应对
0-0平局敲响警钟!泰山队状态堪忧!球迷呼唤战术革新!
深圳侨城东路北延线启动土整!未来南山→龙华快20%
跟着春晚游武汉,这些网红景点旁的地铁站客流激增
精准打击癌细胞,核药研发备受关注
6.14世界献血者日系列科普|成分血捐献是怎么回事儿?
雄鹿与奇才的库兹马-米德尔顿交易:背后的战略考量
30岁还在面基层岗位,这辈子就完了?
四季观星指南
项目经理怎么制定规则
婺源篁岭|赏缤纷晒秋图
从内地去香港卖保险:“月入十万,不包括我”
骆宾王写下大骂武则天的檄文,为何能得到武则天的夸赞?
刷完这15部反诈骗电影,愿你百毒不侵
邯郸历史文化 | 华夏祖庙娲皇宫——国内最大、最早奉祀女娲的地方