浅谈Go语言的通道状态——正常、阻塞、panic、死锁
关于通道,在未初始化、关闭、正常状态下的情况,对其进行读写会出现哪些情况的总结
项目 | 未初始化 | 关闭的通道 |
---|---|---|
关闭操作 | panic | panic |
发送操作 | 死锁 | panic |
接收操作 | 死锁 | 通道缓冲区为空(无缓冲通道视为空),则一直读取0值;否则正常读取 |
项目 | 正常情况(未关闭) |
---|---|
关闭操作 | 正常关闭 |
发送操作 | 阻塞或者正常发送 |
接收操作 | 阻塞或者正常接收 |
来看下面的例子,先别看答案:
//以下程序会不会死锁
func main() {
ch := make(chan int)
go func() {
select {
case <-ch:
}
}()
time.Sleep(1000 * time.Second) //①
}
//以下程序会不会死锁
func main() {
ch := make(chan int)
go func() {
select {
case <-ch:
}
}()
select {} //②
}
前一个程序会不会死锁,后一个程序会死锁
我们再来看一个例子:
//以下程序会不会死锁
func main() {
ch := make(chan int)
go func() {
select {
case <-ch:
}
}()
for { //③
select {
case <-time.After(time.Second):
fmt.Println(1)
}
}
}
不会死锁
这三个例子是为什么呢?为什么单单一个select{},就直接给程序干成了死锁?
妈的,我也不知道怎么解释
第一个例子,虽然程序是sleep在那里,但是本质上Go语言底层也一直在检测是否睡够了时间,说的直白一点就是,此时main函数中,有程序正在执行,并不是什么都没有执行。
第三个例子,其实道理和第一个例子的道理是一样的,就是main中,虽然ch这里一直在阻塞,不过没关系,还有其他的协程(此时是主协程)还在执行就行,也就是说,只要协程没有全部都阻塞就行。
再来一个例子,来对比第二个例子
func main() {
ch := make(chan int)
cht := make(chan int)
go func() {
select {
case <-ch:
}
}()
for {
select {
case <-cht:
// ....
}
}
}
这个例子,毫无疑问,肯定是直接死锁
这个例子其实和第二个例子是一个道理,第二个例子就单单只有一个select{},其实就相当于这个例子,虽然有一个通道cht,但是也是一直阻塞的状态。然而此时呢,main中的所有协程处于全部阻塞的状态,所以直接死锁。
总结:
困扰我很久的问题,这下终于明白了。
在程序没错误(不是由于通道关闭引发的)情况下,在mian函数中,无论启动多少个协程,只要有一个协程是未阻塞的状态,程序就不会死锁;只要main中,全部的协程(包括主协程)都处于阻塞的状态,那么程序就会死锁。