信道分为无缓冲信道(即unbuffered channel)和有缓冲信道(buffered channel)。对于无缓冲的信道来说,我们默认信道的发消息(send)和收消息(receive)都是阻塞(block)的。换句话来说,无缓冲的信道在收消息和发消息的时候,goroutine都处于挂起状态。除非另一端准备好,否则goroutine无法继续往下执行。
无缓冲channel
var ch = make(chan int)
func main() {
ch <- 1
<-ch
}
上面的这段程序在main函数执行到ch <- 1的时候main(也是一个goroutine)便已挂起,而并没有其他goroutine负责接收消息,而下面一句 <-ch 永远无法执行,系统便自动判为timeout返回error。这种所有线程或者进程都在等待资源释放的情况,便称之为死锁。
死锁分类:
- 只在单一goroutine里操作信道
- 串联信道中间一环挂起
- 非缓冲信道不成对出现
注意:并非所有不成对出现的非缓冲信道都会报错
func say(ch chan int) {
ch <- 1
}
func main() {
ch := make(chan int)
go say(ch)
}
虽然say函数挂起等待信道接收消息,但是main goroutine并没有被阻塞,在main函数返回后程序依然可以自动终止。
缓冲channel
func main() {
ch := make(chan int, 3)
ch<-1
ch<-2
ch<-3
//close(ch)
for value := range ch {
fmt.Println(value)
//if len(ch) == 0{
// break
//}
}
}
上述代码在执行完毕后会报deadlock的错误,其原因在于range不会自动检测信道是否干涸(drained),在提取完全部数据后,再次提取会使main函数挂起。
解决方案也有两种,第一种是在for循环中设置长度检测,如果信道为空则跳出循环;第二种是在接收完全部数据后关闭信道。这里值得注意的一点是,关闭状态的信道永远不会阻塞。
第二种方法揭示了信道的另一个特性:对于关闭的信道无法再接收新的数据,但是可以提取其中存留的数据。
总结:
1.在goroutine里读和写channel都会阻塞
2.关闭channel后变成只读,读操作的阻塞会被释放,写操作会报错
3.在main函数里的读写channel不成对出现,会造成死锁
转自:
https://www.jianshu.com/p/147bd63801b6
https://www.jianshu.com/p/082015461b6d