On 17.01.2023 10:57, Brian Candler wrote:
You're supposed to do Add(1) -> Wait; and you'd normally wait once, in
a single goroutine. Typical pattern:
wg.Add(1)
go func() {
defer wg.Done()
... foo
}
wg.Add(1)
go func() {
defer wg.Done()
... bar
}
wg.Wait() // wait for both goroutines to complete
On Tuesday, 17 January 2023 at 04:56:44 UTC bodqh...@gmail.com wrote:
The goroutine has a Wait -> Add(1) -> Done chain.
*Inside* a goroutine? That sounds problematic and racy, as you've found.
- you're Waiting on the same waitgroup in multiple goroutines
concurrently? Then when it counts to zero, multiple goroutines would
be able to start to run
- you're re-adding to a waitgroup even after it's counted down to
zero? That means that "wait" is non-deterministic (it may miss a
count down to zero and back up again).
Well, I dived into the history of this code, and discovered that
initially Wait() was in a separate goroutine. Then another Wait() was
added at the start of this goroutine to prevent it from being run
multiple times in a row, and then the first separate Wait() call was
removed.
I checked with a simple MWE and seems that it should work as expected:
first Wait() before any Add() calls is a no-op, the second call of the
same goroutine is actually blocked until the first one finishes.
@bq:12:01:11:/tmp/dl$ cat a.go
package main
import "fmt"
import "sync"
import "time"
var wg sync.WaitGroup
func main() {
go oink()
time.Sleep(1e9)
go oink()
time.Sleep(3e9)
}
func oink() {
wg.Wait()
wg.Add(1)
fmt.Printf("oink %v\n", time.Now())
time.Sleep(2e9)
wg.Done()
}
@bq:12:01:16:/tmp/dl$ go run a.go
oink 2023-01-22 12:01:19.860472885 +0200 EET m=+0.000090026
oink 2023-01-22 12:01:21.861451472 +0200 EET m=+2.001068963
I still see no reasonable purpose to have the block between Wait() and
Add() to be outside of the critical section, so probably I'll rework it
with a mutex now anyway.
It sounds to me like you need some other synchronization primitive,
but I don't know what it is you're trying to do. Possibly a semaphore.
This video is well worth watching, several times:
https://www.youtube.com/watch?v=5zXAHh5tJqQ
This opened my eyes to a very useful pattern:
- have a one-item buffered channel
- push a value into that channel
- whenever you want to use it: pop it out, use it, and push the
updated value back in
This is a very simple and clean way of doing what would otherwise
require a mutex to protect.
Thanks, I was inspired with this technique several years ago too, but
was disillusioned soon as I found out that it works well only if the
number of calls is predictable (the number of dummy data put into a
channel matches the number of extracted ones for sure). Otherwise, the
channels are pretty fragile, and classic synchronization primitives are
preferable; sometimes I even used the latter to protect channels, as
reading from empty channels leads to deadlocks and writing to closed
channels leads to panics.
Maybe also useful:
https://www.youtube.com/watch?v=f6kdp27TYZs
https://www.youtube.com/watch?v=oV9rvDllKEg
--
You received this message because you are subscribed to a topic in the
Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit
https://groups.google.com/d/topic/golang-nuts/D67p8I5JUuk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to
golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit
https://groups.google.com/d/msgid/golang-nuts/475d96a1-c628-42bd-9fcd-62fe66277599n%40googlegroups.com
<https://groups.google.com/d/msgid/golang-nuts/475d96a1-c628-42bd-9fcd-62fe66277599n%40googlegroups.com?utm_medium=email&utm_source=footer>.
--
You received this message because you are subscribed to the Google Groups
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit
https://groups.google.com/d/msgid/golang-nuts/56f64137-7672-b1f3-ff76-c549ef154f1c%40gmail.com.