協(xié)程死鎖
學完Go的協(xié)程與通道问麸,我們已經(jīng)對Go的并發(fā)編程有大概的了解伙判,可以說go的并發(fā)程序還是很容易編寫的,只要深刻理解go的協(xié)程和通道設計衔憨,日常使用不會出現(xiàn)很大問題叶圃。但凡事都有些許例外情況,就像你去登山越野践图,即使你手中有路線圖掺冠,但現(xiàn)實環(huán)境還是有出入的,你還是有可能踩到陷阱码党。Go的并發(fā)編程有些情況會造成死鎖導致程序退出德崭。
所謂死鎖,Go運行時報錯有個有趣的說法:
fatal error:all goroutines are asleep-deadlock —— 所有的協(xié)程都在睡覺揖盘,可以理解為所有的協(xié)程都在等待資源
下面演示幾種造成死鎖的情況:
1.自我阻塞
一個沒有緩存的管道必須有另一個協(xié)程的讀取才能寫入眉厨,否則當前協(xié)程永遠都在等待寫入管道。
/*自己阻塞自己*/
func BaseDeadlock01() {
//申請一個沒有緩存的管道
ch := make(chan int, 0)
ch <- 123
x := <-ch //零緩存的管道兽狭,有寫必須由另一個協(xié)程讀憾股,否則死鎖
fmt.Println(x)
}
2.協(xié)程開遲了
一個屋緩存的管道鹿蜀,必須先開啟另一個協(xié)程才能寫入,否則協(xié)程等同于沒開——死鎖服球。
/*協(xié)程開晚了*/
func Baseeadlock02() {
ch := make(chan int)
//數(shù)據(jù)應在協(xié)程開開啟后才寫入
ch <- 123
go func() {
x := <-ch
fmt.Println(x)
}()
}
3.互搶資源
管道讀寫時,相互要求對方先讀/寫,自己在寫/讀,造成死鎖
//演示一個錢貨交易的例子
func BaseDeadlock03() {
//無緩存的錢包通道
chMoney := make(chan int)
//無緩存的貨物通道
chGoods := make(chan int)
//商戶子協(xié)程:先給錢再發(fā)貨茴恰!
go func() {
for {
select {
case <-chMoney:
fmt.Println("先給錢再給貨!U缎堋往枣!")
chGoods <- 100
}
}
}()
//客戶主協(xié)程:先發(fā)貨再給錢
for {
select {
case <-chGoods:
fmt.Println("先發(fā)貨再給錢!W怼婉商!")
chMoney <- 100
}
}
}
4.隱性死鎖
有些情況是子協(xié)程直接互相等待各自需要的資源,主協(xié)程沒有發(fā)現(xiàn)而導致隱性死鎖渣叛,這類死鎖運行時不會報錯退出丈秩,但會直接卡死其中的兩個子協(xié)程,占用系統(tǒng)資源
/*讀寫鎖定相互阻塞,形成隱形死鎖*/
func BaseDeadlock04() {
//無緩存的錢包通道
chMoney := make(chan int)
//無緩存的貨物通道
chGoods := make(chan int)
//商戶子協(xié)程:先給錢再發(fā)貨淳衙!
go func() {
for {
select {
case <-chMoney:
fmt.Println("先給錢再給貨D⒒唷!箫攀!")
chGoods <- 100
}
}
}()
?
//客戶主協(xié)程:先發(fā)貨再給錢
go func() {
for {
select {
case <-chGoods:
fmt.Println("先發(fā)貨再給錢3ι!靴跛!")
chMoney <- 100
}
}
}()
//主協(xié)程并不知道其調(diào)用的兩個子協(xié)程在互搶各自都不釋放的資源
for {
time.Sleep(time.Second)
runtime.GC() //通知垃圾回收器清理吧
}
}