英文:
Example for sync.WaitGroup correct?
问题
这是sync.WaitGroup
的正确使用示例吗?它给出了预期的结果,但我对wg.Add(4)
和wg.Done()
的位置不确定。使用wg.Add()
一次性添加四个goroutine是否有意义?
package main
import (
"fmt"
"sync"
"time"
)
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
duration := millisecs * time.Millisecond
time.Sleep(duration)
fmt.Println("Function in background, duration:", duration)
wg.Done()
}
func main() {
var wg sync.WaitGroup
wg.Add(4)
go dosomething(200, &wg)
go dosomething(400, &wg)
go dosomething(150, &wg)
go dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
结果(如预期):
Function in background, duration: 150ms
Function in background, duration: 200ms
Function in background, duration: 400ms
Function in background, duration: 600ms
Done
英文:
Is this example usage of sync.WaitGroup
correct? It gives the expected result, but I am unsure about the wg.Add(4)
and the position of wg.Done()
. Does it make sense to add the four goroutines at once with wg.Add()
?
http://play.golang.org/p/ecvYHiie0P
package main
import (
"fmt"
"sync"
"time"
)
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
duration := millisecs * time.Millisecond
time.Sleep(duration)
fmt.Println("Function in background, duration:", duration)
wg.Done()
}
func main() {
var wg sync.WaitGroup
wg.Add(4)
go dosomething(200, &wg)
go dosomething(400, &wg)
go dosomething(150, &wg)
go dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
Result (as expected):
Function in background, duration: 150ms
Function in background, duration: 200ms
Function in background, duration: 400ms
Function in background, duration: 600ms
Done
答案1
得分: 157
是的,这个例子是正确的。重要的是wg.Add()
发生在go
语句之前,以防止竞态条件。以下的代码也是正确的:
func main() {
var wg sync.WaitGroup
wg.Add(1)
go dosomething(200, &wg)
wg.Add(1)
go dosomething(400, &wg)
wg.Add(1)
go dosomething(150, &wg)
wg.Add(1)
go dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
然而,当你已经知道Add()
将被调用多少次时,反复调用wg.Add
是没有意义的。
WaitGroup在计数器低于零时会引发panic。计数器从零开始,每个Done()
是一个-1
,每个Add()
取决于参数。因此,为了确保计数器永远不会降到零以下并避免panic,你需要确保Add()
在Done()
之前保证发生。
在Go中,这样的保证是由内存模型提供的。
内存模型规定,单个goroutine中的所有语句看起来是按照它们编写的顺序执行的。实际上它们可能不会按照这个顺序执行,但结果就好像是按照这个顺序执行的。还保证了goroutine在调用它的go
语句之后才运行。由于Add()
发生在go
语句之前,而go
语句发生在Done()
之前,我们知道Add()
发生在Done()
之前。
如果你将go
语句放在Add()
之前,程序可能会正常运行。然而,这将是一种竞态条件,因为不能保证它的正确性。
英文:
Yes, this example is correct. It is important that the wg.Add()
happens before the go
statement to prevent race conditions. The following would also be correct:
func main() {
var wg sync.WaitGroup
wg.Add(1)
go dosomething(200, &wg)
wg.Add(1)
go dosomething(400, &wg)
wg.Add(1)
go dosomething(150, &wg)
wg.Add(1)
go dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
However, it is rather pointless to call wg.Add
over and over again when you already know how many times it will be called.
Waitgroups
panic if the counter falls below zero. The counter starts at zero, each Done()
is a -1
and each Add()
depends on the parameter. So, to ensure that the counter never drops below and avoid panics, you need the Add()
to be guaranteed to come before the Done()
.
In Go, such guarantees are given by the memory model.
The memory model states that all statements in a single goroutine appear to be executed in the same order as they are written. It is possible that they won't actually be in that order, but the outcome will be as if it was. It is also guaranteed that a goroutine doesn't run until after the go
statement that calls it. Since the Add()
occurs before the go
statement and the go
statement occurs before the Done()
, we know the Add()
occurs before the Done()
.
If you were to have the go
statement come before the Add()
, the program may operate correctly. However, it would be a race condition because it would not be guaranteed.
答案2
得分: 30
我建议将wg.Add()
调用嵌入到doSomething()
函数本身中,这样如果你调整了它的调用次数,就不必手动单独调整添加参数,否则如果你更新了一个但忘记更新另一个,可能会导致错误(在这个简单的示例中可能不太可能发生,但我个人认为这是更好的代码重用实践)。
正如Stephen Weinberg在他对这个问题的回答中指出的那样,你必须在生成gofunc之前增加waitgroup的计数,但你可以通过将gofunc生成包装在doSomething()
函数本身中来轻松实现这一点,就像这样:
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
wg.Add(1)
go func() {
duration := millisecs * time.Millisecond
time.Sleep(duration)
fmt.Println("Function in background, duration:", duration)
wg.Done()
}()
}
然后你可以在不使用go
调用的情况下调用它,例如:
func main() {
var wg sync.WaitGroup
dosomething(200, &wg)
dosomething(400, &wg)
dosomething(150, &wg)
dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
作为一个示例:http://play.golang.org/p/WZcprjpHa_
英文:
I would recommend embeding the wg.Add()
call into the doSomething()
function itself, so that if you adjust the number of times it's called, you don't have to separately adjust the add parameter manually which could lead to an error if you update one but forget to update the other (in this trivial example that is unlikely, but still, I personally believe it to be better practice for code re-use).
As Stephen Weinberg points out in his answer to this question, you do have to increment the waitgroup prior to spawning the gofunc, but you can accomplish this easily by wrapping the gofunc spawn inside the doSomething()
function itself, like this:
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
wg.Add(1)
go func() {
duration := millisecs * time.Millisecond
time.Sleep(duration)
fmt.Println("Function in background, duration:", duration)
wg.Done()
}()
}
Then you can call it without the go
invocation, e.g.:
func main() {
var wg sync.WaitGroup
dosomething(200, &wg)
dosomething(400, &wg)
dosomething(150, &wg)
dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
As a playground: http://play.golang.org/p/WZcprjpHa_
答案3
得分: 24
- 在Mroth的答案基础上进行了小的改进
- 使用defer来确保Done的安全性
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
wg.Add(1)
go func() {
defer wg.Done()
duration := millisecs * time.Millisecond
time.Sleep(duration)
fmt.Println("后台函数,持续时间:", duration)
}()
}
func main() {
var wg sync.WaitGroup
dosomething(200, &wg)
dosomething(400, &wg)
dosomething(150, &wg)
dosomething(600, &wg)
wg.Wait()
fmt.Println("完成")
}
英文:
- small improvement based on Mroth answer
- using defer for Done is safer
> func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
> wg.Add(1)
> go func() {
> defer wg.Done()
> duration := millisecs * time.Millisecond
> time.Sleep(duration)
> fmt.Println("Function in background, duration:", duration)
> }()
> }
>
> func main() {
> var wg sync.WaitGroup
> dosomething(200, &wg)
> dosomething(400, &wg)
> dosomething(150, &wg)
> dosomething(600, &wg)
> wg.Wait()
> fmt.Println("Done")
> }
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论