Go deadlock 初體驗(yàn)

為了學(xué)習(xí) Go 通過 channel 實(shí)現(xiàn)同步的機(jī)制,我寫了以下代碼來試驗(yàn):

import (
 "fmt"
 "sync"
)
?
var (
 counter int64
 wg      sync.WaitGroup
)
?
func main() {
 ch := make(chan int64)
?
 wg.Add(2)
?
 go incCounter(ch)
 go incCounter(ch)
 ch <- counter
?
 // wait until two goroutines exit
 wg.Wait()
?
 // expected value is 4
 fmt.Println("Final Counter:", counter)
}
func incCounter(ch chan int64) {
 defer wg.Done()
?
 for count := 0; count < 2; count++ {
 // receive data from channel
 value := <-ch
?
 value++
 counter = value
?
 // send data to channel
 ch <- counter
 }
}

上面的代碼很簡單,就是有兩個 goroutine 共享counter這個變量進(jìn)行讀寫操作,counter最終的輸出值應(yīng)該是4。為了避免出現(xiàn)不同步的情況,把counter放入一個無緩沖的 channel 中,通過這個 channel 在兩個 goroutine 之間傳遞counter

運(yùn)行之后程序報(bào)錯:

fatal error: all goroutines are asleep - deadlock!

這意味著所有 goroutine 都被阻塞了,整個程序進(jìn)入死鎖狀態(tài),可是為什么?

第一個坑

一開始我是百思不得其解,于是就在 Stack Overflow 上求助,有位哥們一語道破天機(jī)。他解釋道,上面這段程序的執(zhí)行流程是這樣的:

deadlock.jpg

原來是沒有 goroutine 在最后從 channel 中取出counter,而這又是個無緩沖的 channel,所有 goroutine 都被阻塞了,自然也就死鎖了。

解決方法

很簡單,在main中把fmt.Println("Final Counter:", counter)修改為:

fmt.Println("Final Counter:", <-ch)

也就是讓主 goroutine 負(fù)責(zé)取出最終的counter。

第二個坑

本來以為這樣就行了,沒想到運(yùn)行之后還是死鎖。我 debug 了很久之后終于找出了第二個坑。

程序中使用了sync.WaitGroup這個類型來阻止主 goroutine 在其他子 goroutine 之前終止,也就是不讓main函數(shù)提前退出。wg.Add(2)就是告訴main要等兩個 goroutine 終止之后才能退出,而在 goroutine 中則是通過wg.Done()來通知main函數(shù)某個 goroutine 已終止。

wg.Done()前面加上了一個defer,也就是要等到incCounter中其他代碼都執(zhí)行完了才會調(diào)用wg.Done()。這就是問題所在,像上面那張圖展示的那樣,goroutine 2 把counter放入 channel 中,等著主 goroutine 取出;但在main函數(shù)中, fmt.Println("Final Counter:", <-ch)之前有一句wg.Wait(),也就是說要等wg.Wait()方法退出了才能執(zhí)行下一句。然而此時這個方法并沒有退出,因?yàn)?goroutine 2 還在被ch <- counter阻塞中,也就沒法退出循環(huán)體,那么也就沒法執(zhí)行wg.Done()!程序也就又死鎖了。

解決

必須手動調(diào)用wg.Done()而不能依賴defer的延遲調(diào)用機(jī)制:

func incCounter(ch chan int64) {
 for count := 0; count < 2; count++ {
 // receive data from channel
 value := <-ch
?
 value++
 counter = value
?
 // if this goroutine has incremented counter twice,
 // it will exit
 if count == 1 {
 wg.Done()
 }
?
 // send data to channel
 ch <- counter
 }
}

至此,程序終于可以正常運(yùn)行,counter最后的輸出為4,兩個 goroutine 實(shí)現(xiàn)了同步。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容