本文是自己对相关知识运用和理解的一些领悟,带着自己当初的疑惑疑问和大家分享交流,也欢迎大家留言分享自己的总结哦!
go中的通道就是channel,用于goroutine之间的通讯/数据的传递。可以想象成一个管,一端有人操作发送,一端有人操作接收,如果发送端没有准备好不能发送,即使接收端能正常接收那也不能拿到数据,发送端能正常发送但是接收端不能正常接收,那么整个流程也是不流通的。只有两端都能保证正常发送和接收,数据才能正常流通,你的想法才能实现。
定位:传递消息/数据(通知);使某个goroutine阻塞
goroutine可以理解为线程但比线程轻。以下每个goroutine可以看作一条线,比如main就是main线。
看下面的代码,main函数中只有main一个goroutine(main线程可以看作一个goroutine),由于ch是无缓冲通道,也就是不能暂存数据,只能传递,当main线执行ch <- 1时没有其他goroutine负责接收消息,因此发送者阻塞,main线挂起,此时下面的所有代码将不会执行
var ch = make(chan int)
func main() {
ch <- 1
//若干代码
}
所以它会提示:
fatal error: all goroutines are asleep - deadlock!
即没人来接收我要发的数据,我怎么给你发过去?所以我先睡了。
注意,以下两种情况是两码事:
1,发送端正常但接收端无goroutine
2,发送端与接收端goroutine都在运行,但接收端等不到数据(发送端并无数据发送)
此代码中只有一个goroutine, 所以向里面加数据或者存数据,都会锁死通道, 并且阻塞当前 goroutine, 也就是所有的goroutine(其实就main线一个)都在等待通道的开放(没人拿走数据通道是不会开放的),也就是死锁。关于go中通道死锁的具体情况分析传送门:go中通道的死锁分析
无缓冲通道 & 有缓冲通道
1,无缓冲的通道在取消息和存消息时都会挂起当前的goroutine,除非管的另一端已准备好
2,有缓冲通道:存消息时如果缓冲区大小大于等于存入的消息数量,那么不会阻塞,反之和无缓冲一样(挂起当前的goroutine)
3,从无缓冲通道取数据,必须要有数据流进来才可以,否则当前线阻塞;数据流入无缓冲通道, 如果没有其他goroutine来拿走这个数据,那么当前线阻塞
channel的底层实现是基于hchan结构体,结构体中的buf字段就是用来存放缓冲区的数据,它是一个环形队列,又环形链表实现。
看下面的代码,函数loop()被调用两次,两次的goroutine分别是:go关键字开启的goroutine和main线
func loop() {
for i := 0; i < 10; i++ {
fmt.Printf("%d ", i)
}
}
func main() {
go loop()
loop()
}
结果是只输出一趟
main线退出的太快,他的loop执行完了但另一个goroutine还没来得及执行。
即要让你的groutine生效,最简单的办法是让main线程延迟退出,阻塞主线:
func main() {
go loop()
loop()
time.Sleep(time.Second)
}
go中通道就扮演的是这样的角色!用通道怎么通知main线呢?请看下面的代码:
var complete chan int = make(chan int)
func loop() {
for i := 0; i < 10; i++ {
fmt.Printf("%d ", i)
}
complete <- 0 // 执行完毕了,发个消息,以0作为标志(此处就完美体现了channel的通知作用)
}
func main() {
go loop()
<- complete // 如果取到消息了说明另一个goroutine跑完了. 否则main在此阻塞住
}
main函数的 <- complete 其实就是阻塞住main线不让main线过早跑完,直到main线从complete中读出消息(0)。
关于channel的控制并发实战可移步https://blog.csdn.net/HYZX_9987/article/details/101201408