将sync.WaitGroup与外部函数一起使用的最佳方法

时间:2016-04-04 15:46:15

标签: go synchronization

我在使用以下代码时遇到了一些问题:

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)
    }

}

什么是最佳解决方案?

2 个答案:

答案 0 :(得分:16)

好吧,首先您的实际错误是您为Print方法提供了sync.WaitGroup的副本,因此它不会调用您Done()上的Wait()方法重新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方法以删除它的*.VC.db通常是个好主意:该方法不需要知道有什么东西在等待它完成它的工作。

答案 1 :(得分:1)

我同意@ Elwinar的解决方案,即代码中的主要问题是将Waitgroup的副本传递给Print函数。

这意味着wg.Done()是在wg中定义的main副本上运行的。因此,wg中的main无法降低,因此当你在wg.Wait()中发生死锁时会发生死锁。

既然你也在询问最佳做法,我可以给你一些自己的建议:

  • 请勿删除defer wg.Done()中的Print。由于你的main中的goroutine是发送者,而print是接收者,因此在接收器例程中删除wg.Done()将导致未完成的接收者。这是因为只有您的发件人与您的主邮件同步,因此在您的发件人完成后,您的主邮件已完成,但接收器可能仍在工作。我的观点是:在你的主程序完成后不要留下一些悬垂的goroutines。关闭它们或等待它们。

  • 记得要到处都做恐慌,特别是匿名的goroutine。我已经看到很多golang程序员忘记在goroutines中进行恐慌恢复,即使他们记得将恢复恢复到正常功能中。当您希望代码正常运行或至少在遇到意外情况时更优雅时,这一点至关重要。

  • 在每次重要通话之前使用defer,例如sync相关电话,开头,因为您不知道代码可能会中断的位置。假设您在defer之前删除了wg.Done(),并且在您的示例中,您的匿名goroutine发生了恐慌。如果你没有恐慌恢复,它会恐慌。但是,如果你有恐慌恢复会发生什么?现在一切都很好吗?不会。由于恐慌导致wg.Wait()被忽略,您将在wg.Done()遇到僵局!但是,通过使用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")
}

希望有所帮助:)