死锁常见于有多个协程阻塞,而当主协程中如果有channel发生阻塞,则必然死锁,死锁的直接原因在于channel的读与写没有成对出现,只有读,或者只有写都会造成死锁,特殊情况除外,这个后面会提到。
死锁的发生离不开阻塞,channel的阻塞分两种:
1.对于无缓冲channel,因为其容量为0,数据只读不写,或者只写不读都会造成堵塞
2.对于有缓冲channel,因为设置了容量,在channel容量满之前,仅往里面写数据而不读取是不会导致阻塞的,不会使得协程挂起,但如果channel满了还往里写入数据,就会导致阻塞
下面列举几种死锁的场景
1.只有一个协程(main)时,只往channel读数据或者写数据
package main
import "fmt"
func main() {
ch1 := make(chan int)
fmt.Println(<-ch1)
ch := make(chan int, 3)
// 在无数据填入ch时,执行fmt.Println(<-ch),会发生死锁
// fmt.Println(<-ch)
ch <- 1
ch <- 2
ch <- 3
for v := range ch {
fmt.Println(v)
//在读取完ch时,若不关闭ch,继续读取,也同样会发生死锁
if v==3{
close(ch)
}
}
}
反例来了,将上面的代码放到子协程中,并不会发生死锁,但也不会有任何输出,代码如下,原因是write(ch)在执行 fmt.Println(<-ch)时,因为ch为空,此时write协程被挂起,主协程并没有等待write协程执行完而直接结束,因此并没有死锁
import "fmt"
func main(){
ch := make(chan int, 3)
// 在无数据填入ch时,执行fmt.Println(<-ch),会发生死锁
fmt.Println(<-ch)
//ch <- 1
//ch <- 2
//ch <- 3
go write(ch)
go read(ch)
//for v := range ch {
// fmt.Println(v)
// //在读取完ch时,若不关闭ch,继续读取,也同样会发生死锁
// if v==3{
// close(ch)
// }
//}
}
func write(ch chan int){
fmt.Println(<-ch)
ch <- 1
ch <- 2
ch <- 3
}
func read(ch chan int){
for v := range ch {
fmt.Println(v)
//在读取完ch时,若不关闭ch,继续读取,也同样会发生死锁
if v==3{
close(ch)
}
}
}