简体   繁体   English

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

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

I have some issues with the following code: 我有以下代码的一些问题:

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

I get a deadlock at the specified place. 我在指定的地方陷入僵局。 I have tried setting wg.Add(1) instead of 2 and it solves my problem. 我试过设置wg.Add(1)而不是2,它解决了我的问题。 My belief is that I'm not successfully sending the channel as an argument to the Printer function. 我的信念是,我没有成功发送频道作为Printer功能的参数。 Is there a way to do that? 有没有办法做到这一点? Otherwise, a solution to my problem is replacing the go Print(ch, wg) line with: 否则,我的问题的解决方案是将go Print(ch, wg)行替换为:

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

and changing the Printer function to: 并将Printer功能更改为:

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

}

What is the best solution? 什么是最好的解决方案?

Well, first your actual error is that you're giving the Print method a copy of the sync.WaitGroup , so it doesn't call the Done() method on the one you're Wait() ing on. 好吧,首先你的实际错误是你给了Print方法一个sync.WaitGroup的副本,所以它不会在你Wait()那个上调用Done()方法。

Try this instead: 试试这个:

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

Now, changing your Print method to remove the WaitGroup of it is a generally good idea: the method doesn't need to know something is waiting for it to finish its job. 现在,更改Print方法以删除它的WaitGroup通常是一个好主意:该方法不需要知道有什么东西在等待它完成它的工作。

I agree with @Elwinar's solution, that the main problem in your code caused by passing a copy of your Waitgroup to the Print function. 我同意@ Elwinar的解决方案,即代码中的主要问题是将Waitgroup的副本传递给Print函数。

This means the wg.Done() is operated on a copy of wg you defined in the main . 这意味着wg.Done()在你在main定义的wg副本上运行。 Therefore, wg in the main could not get decreased, and thus a deadlock happens when you wg.Wait() in main. 因此, main wg无法降低,因此当你在main中wg.Wait()时会发生死锁。

Since you are also asking about the best practice, I could give you some suggestions of my own: 既然你也在询问最佳实践,我可以给你一些我自己的建议:

  • Don't remove defer wg.Done() in Print . 不要在Print删除defer wg.Done() Since your goroutine in main is a sender, and print is a receiver, removing wg.Done() in receiver routine will cause an unfinished receiver. 由于你的main中的goroutine是发送者,而print是接收者, wg.Done()在接收器例程中删除wg.Done()将导致未完成的接收者。 This is because only your sender is synced with your main, so after your sender is done, your main is done, but it's possible that the receiver is still working. 这是因为只有您的发件人与您的主邮件同步,因此在您的发件人完成后,您的主邮件已完成,但接收器可能仍在工作。 My point is: don't leave some dangling goroutines around after your main routine is finished. 我的观点是: 在主程序结束后,不要留下一些悬垂的goroutines。 Close them or wait for them. 关闭它们或等待它们。

  • Remember to do panic recovery everywhere, especially anonymous goroutine. 记得到处都做恐慌,特别是匿名的goroutine。 I have seen a lot of golang programmers forgetting to put panic recovery in goroutines, even if they remember to put recover in normal functions. 我已经看到很多golang程序员忘记在goroutines中进行恐慌恢复,即使他们记得将恢复恢复到正常功能中。 It's critical when you want your code to behave correctly or at least gracefully when something unexpected happened. 当您希望代码在发生意外情况时正常运行或至少优雅地运行时,这一点至关重要。

  • Use defer before every critical calls, like sync related calls, at the beginning since you don't know where the code could break. 使用defer每一个重要的电话之前,像sync相关的电话, 一开始 ,因为你不知道在哪里的代码可能会断裂。 Let's say you removed defer before wg.Done() , and a panic occurrs in your anonymous goroutine in your example. 假设你在wg.Done()之前删除了defer ,并且在你的例子中你的匿名goroutine发生恐慌。 If you don't have panic recover, it will panic. 如果你没有恐慌恢复,它会恐慌。 But what happens if you have a panic recover? 但是,如果你有恐慌恢复会发生什么? Everything's fine now? 现在一切都很好吗? No. You will get deadlock at wg.Wait() since your wg.Done() gets skipped because of panic! 不会。因为你的wg.Done()因为恐慌而被跳过,所以你会在wg.Wait()遇到死锁! However, by using defer , this wg.Done() will be executed at the end, even if panic happened. 但是,通过使用defer ,即使发生恐慌,也会在结束时执行此wg.Done() Also, defer before close is important too, since its result also affects the communication. 此外,在close之前推迟也很重要,因为其结果也会影响通信。

So here is the code modified according to the points I mentioned above: 所以这里是根据我上面提到的点修改的代码:

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

Hope it helps :) 希望能帮助到你 :)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM