在 go
中常常會使用 channel
,進行併發執行子任務,提升執行效率。但一不當心就會踩到 deadlock
的坑,本文就來解析一下常見的死鎖形式和解決方式。golang
chan
產生死鎖package main import ( "fmt" ) func main() { ch := make(chan int, 3) <-ch }
輸出結果:併發
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() /home/work/code/golang/src/interview/go/deadlock/test.go:9 +0x56 Process finished with exit code 2
解決方式:異步
採用 select case default
阻塞默認處理方式。spa
package main import ( "fmt" ) func main() { ch := make(chan int, 3) select { case v := <-ch: fmt.Println(v) default: fmt.Println("chan no data") } }
channel
產生死鎖package main import "fmt" func main() { ch := make(chan int) ch <- 1 // 無緩衝在此寫入數據,卻沒有讀數據,阻塞住 fmt.Println(<-ch) // 被上面阻塞,沒法被執行到 }
輸出結果:線程
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan send]: main.main() /home/work/code/golang/src/interview/go/deadlock/test02.go:8 +0x59 Process finished with exit code 2
解決方式:code
a. 採用開啓子協程方式,保證讀寫 chan
成對存取數據;協程
package main import "fmt" func main() { ch := make(chan int) go func() { ch <- 1 // 開啓子goroutine寫入數據 }() fmt.Println(<-ch) // 阻塞住,一旦ch有數據,則讀取成功 }
b. 採用有緩衝 chan
,在容量範圍內不會阻塞;隊列
package main import "fmt" func main() { ch := make(chan int, 1) ch <- 1 fmt.Println(<-ch) }
chan
超過容量時產生死鎖package main import ( "fmt" ) func main() { ch := make(chan int, 3) ch <- 1 ch <- 2 ch <- 3 ch <- 4 // 超過最大容量,阻塞main協程,產生deadlock for v := range ch { fmt.Println(v) } }
解決方式:資源
a. 增長緩衝容量,保證能知足寫入全部數據;rem
b. 採用 select case default
阻塞默認處理方式(demo
略);
for range
產生死鎖package main import ( "fmt" ) func main() { ch := make(chan int, 3) ch <- 1 ch <- 2 ch <- 3 // range 一直讀取直到chan關閉,不然產生阻塞死鎖 for v := range ch { fmt.Println(v) } }
輸出結果:
1 2 3 fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() /home/work/code/golang/src/interview/go/deadlock/test04.go:15 +0x115 Process finished with exit code 2
解決方式:
a. 顯式關閉 channel
;
b. 開啓子協程,主協程 sleep
等待時間後退出;
package main import ( "fmt" "time" ) func main() { ch := make(chan int, 3) ch <- 1 ch <- 2 ch <- 3 close(ch) // 解決方式1:關閉chan // range 一直讀取直到chan關閉,不然產生阻塞死鎖 // 解決方式2:開啓子協程,主協程sleep等待 go func() { for v := range ch { fmt.Println(v) } }() time.Sleep(1e9) }
【小結】
channel
中,要注意區分有緩衝(buffered channel
,異步隊列-FIFO處理)與無緩衝(unbuffered channel
,同步流入流出)通道的區別,掌握各自適合使用的方式;deadlock
必定是線程/協程之間存在了資源競爭,互相佔用對方須要的資源致使程序永遠不能退出,須要當心可能遇到的坑,也能夠經過加鎖避免。