簡體   English   中英

將sync.WaitGroup與外部函數一起使用的最佳方法

[英]Best way of using sync.WaitGroup with external function

我有以下代碼的一些問題:

package main

import (
"fmt"
"sync"
)
// This program should go to 11, but sometimes it only prints 1 to 10.
func main() {
    ch := make(chan int)
    var wg sync.WaitGroup
    wg.Add(2)
    go Print(ch, wg) //
    go func(){

        for i := 1; i <= 11; i++ {
            ch <- i
        }

        close(ch) 
        defer wg.Done()


    }()

    wg.Wait() //deadlock here
}

// Print prints all numbers sent on the channel.
// The function returns when the channel is closed.
func Print(ch <-chan int, wg sync.WaitGroup) {
    for n := range ch { // reads from channel until it's closed
        fmt.Println(n)
    }
    defer wg.Done()
}

我在指定的地方陷入僵局。 我試過設置wg.Add(1)而不是2,它解決了我的問題。 我的信念是,我沒有成功發送頻道作為Printer功能的參數。 有沒有辦法做到這一點? 否則,我的問題的解決方案是將go Print(ch, wg)行替換為:

go func() {
Print(ch)
defer wg.Done()
}

並將Printer功能更改為:

func Print(ch <-chan int) {
    for n := range ch { // reads from channel until it's closed
        fmt.Println(n)
    }

}

什么是最好的解決方案?

好吧,首先你的實際錯誤是你給了Print方法一個sync.WaitGroup的副本,所以它不會在你Wait()那個上調用Done()方法。

試試這個:

package main

import (
    "fmt"
    "sync"
)

func main() {    
    ch := make(chan int)

    var wg sync.WaitGroup
    wg.Add(2)    

    go Print(ch, &wg)

    go func() {  
        for i := 1; i <= 11; i++ {
            ch <- i
        }
        close(ch)
        defer wg.Done()
    }()          

    wg.Wait() //deadlock here
}                

func Print(ch <-chan int, wg *sync.WaitGroup) {
    for n := range ch { // reads from channel until it's closed
        fmt.Println(n)
    }            
    defer wg.Done()
}

現在,更改Print方法以刪除它的WaitGroup通常是一個好主意:該方法不需要知道有什么東西在等待它完成它的工作。

我同意@ Elwinar的解決方案,即代碼中的主要問題是將Waitgroup的副本傳遞給Print函數。

這意味着wg.Done()在你在main定義的wg副本上運行。 因此, main wg無法降低,因此當你在main中wg.Wait()時會發生死鎖。

既然你也在詢問最佳實踐,我可以給你一些我自己的建議:

  • 不要在Print刪除defer wg.Done() 由於你的main中的goroutine是發送者,而print是接收者, wg.Done()在接收器例程中刪除wg.Done()將導致未完成的接收者。 這是因為只有您的發件人與您的主郵件同步,因此在您的發件人完成后,您的主郵件已完成,但接收器可能仍在工作。 我的觀點是: 在主程序結束后,不要留下一些懸垂的goroutines。 關閉它們或等待它們。

  • 記得到處都做恐慌,特別是匿名的goroutine。 我已經看到很多golang程序員忘記在goroutines中進行恐慌恢復,即使他們記得將恢復恢復到正常功能中。 當您希望代碼在發生意外情況時正常運行或至少優雅地運行時,這一點至關重要。

  • 使用defer每一個重要的電話之前,像sync相關的電話, 一開始 ,因為你不知道在哪里的代碼可能會斷裂。 假設你在wg.Done()之前刪除了defer ,並且在你的例子中你的匿名goroutine發生恐慌。 如果你沒有恐慌恢復,它會恐慌。 但是,如果你有恐慌恢復會發生什么? 現在一切都很好嗎? 不會。因為你的wg.Done()因為恐慌而被跳過,所以你會在wg.Wait()遇到死鎖! 但是,通過使用defer ,即使發生恐慌,也會在結束時執行此wg.Done() 此外,在close之前推遲也很重要,因為其結果也會影響通信。

所以這里是根據我上面提到的點修改的代碼:

package main

import (
    "fmt"
    "sync"
)

func main() {
    ch := make(chan int)
    var wg sync.WaitGroup
    wg.Add(2)
    go Print(ch, &wg)
    go func() {

        defer func() {
            if r := recover(); r != nil {
                println("panic:" + r.(string))
            }
        }()

        defer func() {
            wg.Done()
        }()

        for i := 1; i <= 11; i++ {
            ch <- i

            if i == 7 {
                panic("ahaha")
            }
        }

        println("sender done")
        close(ch)
    }()

    wg.Wait()
}

func Print(ch <-chan int, wg *sync.WaitGroup) {
    defer func() {
        if r := recover(); r != nil {
            println("panic:" + r.(string))
        }
    }()

    defer wg.Done()

    for n := range ch {
        fmt.Println(n)
    }
    println("print done")
}

希望能幫助到你 :)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM