[英]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.