亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

為什么在 defer 語句中關閉通道會出現恐慌?

為什么在 defer 語句中關閉通道會出現恐慌?

Go
HUH函數 2023-07-31 10:50:34
在下面的示例中,go 例程將值泵入無緩沖通道,并且 main 函數對其進行迭代。package mainimport (    "fmt"    "strconv")var chanStr chan stringfunc main() {    go pump()    fmt.Println("iterating ...")    for val := range chanStr {        fmt.Printf("fetched val: %s from channel\n", val)    }}func pump() {    defer close(chanStr)    chanStr = make(chan string)    for i := 1; i <= 5; i++ {        fmt.Printf("pumping seq %d into channel\n", i)        chanStr <- "val" + strconv.Itoa(i)    }    //close(chanStr)}該函數會出現恐慌并輸出以下內容:iterating ...                                             pumping seq 1 into channel                                pumping seq 2 into channel                                fetched val: val1 from channel                            ......fetched val: val4 from channel                            pumping seq 5 into channel                                panic: close of nil channel                               goroutine 5 [running]:                                    main.pump()                                                       C:/personal/gospace/go-rules/test.go:26 +0x1a6    created by main.main                                              C:/personal/gospace/go-rules/test.go:11 +0x4e     但是,如果我評論 defer 語句并在 goroutine 中的 for 循環之后立即關閉pump,接收器不會驚慌。 這兩種情況有什么區別?看起來 defer 在收到值之前關閉通道,但常規關閉會等待。此外,當我使用競爭檢測器進行構建時,即使在常規關閉中,它也會標記潛在的競爭條件(我無法每次都重新創建競爭)。這是否意味著這兩種方式都不能優雅地關閉通道?更新: 對于所有評論,我知道問題是什么。我必須在函數的第一行創建通道main()。不過,我在 Windows 上運行 go1.12,并且觀察到了這種行為。顯然我沒有偽造輸出。我一直使用 defer 語句重新創建恐慌,甚至在 for 循環后立即關閉通道時也沒有發生過恐慌pump()
查看完整描述

3 回答

?
梵蒂岡之花

TA貢獻1900條經驗 獲得超5個贊

您的代碼在不同方面都非常活潑:


for val在 goroutine 實際初始化通道之前,您有可能(事實上,很有可能)開始從循環中的通道讀取數據,從而導致死鎖。


iterating ...

pumping seq 1 into channel

fatal error: all goroutines are asleep - deadlock!

事實上,這是我觀察到的在本地或操場上按原樣執行代碼的唯一行為。


如果我添加一個延遲,


 fmt.Println("iterating ...")

 time.Sleep(10 * time.Millisecond) // Delay ensures the channel has been created

 for val := range chanStr {

然后我確實觀察了您注意到的行為:


iterating ...

pumping seq 1 into channel

fetched val: val1 from channel

pumping seq 2 into channel

pumping seq 3 into channel

fetched val: val2 from channel

fetched val: val3 from channel

pumping seq 4 into channel

pumping seq 5 into channel

fetched val: val4 from channel

fetched val: val5 from channel

panic: close of nil channel

原因是你在調用close(chanStr)時chanStr仍然為零。defer如果您在創建頻道后致電您:


func pump() {

    chanStr = make(chan string)

    defer close(chanStr)

你會解決這個問題的。


要解決這兩個競爭,您需要在調用 goroutine之前初始化通道。完整代碼:


package main


import (

    "fmt"

    "strconv"

)


var chanStr chan string


func main() {

    chanStr = make(chan string)

    go pump(chanStr)

    fmt.Println("iterating ...")

    for val := range chanStr {

        fmt.Printf("fetched val: %s from channel\n", val)

    }

}


func pump(chanStr chan string) {

    defer close(chanStr)

    for i := 1; i <= 5; i++ {

        fmt.Printf("pumping seq %d into channel\n", i)

        chanStr <- "val" + strconv.Itoa(i)

    }

}

為了進一步說明問題是立即defer close(chanStr)評估chanStr(當它仍然是nil)時,請考慮這個(不推薦?。┨娲鉀Q方案:


package main


import (

    "fmt"

    "strconv"

    "time"

)


var chanStr chan string


func main() {

    go pump()

    fmt.Println("iterating ...")

    time.Sleep(10 * time.Millisecond)

    for val := range chanStr {

        fmt.Printf("fetched val: %s from channel\n", val)

    }

}


func pump() {

    defer func() {

        close(chanStr)

    }()

    chanStr = make(chan string)

    for i := 1; i <= 5; i++ {

        fmt.Printf("pumping seq %d into channel\n", i)

        chanStr <- "val" + strconv.Itoa(i)

    }

}

在這種情況下,延遲函數是 的閉包chanStr,因此chanStr的計算被延遲到實際執行。在此版本中,當延遲函數執行時,chanStr不再為 nil,因此無需恐慌。


查看完整回答
反對 回復 2023-07-31
?
手掌心

TA貢獻1942條經驗 獲得超3個贊

主 go 例程有可能在創建通道之前從通道中讀取數據。這就是你的數據競賽。

應在開始您的 go 例程之前創建通道。

修復: https: //play.golang.org/p/O7pgM05KEtI


查看完整回答
反對 回復 2023-07-31
?
慕標5832272

TA貢獻1966條經驗 獲得超4個贊

您發布的代碼存在死鎖情況。您可能沒有發布相同的代碼。


這是應該可以工作的更新后的代碼:


package main


import (

? ? "fmt"

? ? "strconv"

)


func main() {


? ? chanStr := make(chan string)


? ? go pump(chanStr)

? ? fmt.Println("iterating ...")

? ? for val := range chanStr {

? ? ? ? fmt.Printf("fetched val: %s from channel\n", val)

? ? }

}


func pump(ch chan string) {

? ? defer close(ch)


? ? for i := 1; i <= 5; i++ {

? ? ? ? fmt.Printf("pumping seq %d into channel\n", i)

? ? ? ? ch <- "val" + strconv.Itoa(i)

? ? }

? ? //close(chanStr)

}


本例中的問題是,您在函數內部創建了通道pump,因此主函數不知道如何使用數據,并且由于沒有使用者而導致死鎖。


查看完整回答
反對 回復 2023-07-31
  • 3 回答
  • 0 關注
  • 243 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號