I didn't intend Add to be used concurrently, by the way. One pool one thread for the controller.
On Sunday, 5 May 2019 21:09:40 UTC+2, Louki Sumirniy wrote: > > If at 13 in the else clause, if I first test for a non-nil wg.ops and > close it if it's open, I think that stops that channel leak. > > On Sunday, 5 May 2019 18:32:14 UTC+2, Marcin Romaszewicz wrote: >> >> I've done quite a bit of MP programming over 20+ years now, and skimming >> your code, I see a number of issues. There are probably a lot more that I >> don't. In the latest go playground link >> >> Line14: if "if wg.started {" has a race condition, both on accessing the >> variable, and logically, in that two goroutines can come into the function >> and both go down the wg.started = false path. You can't do this without a >> synchronization primitive. >> >> Line 17: (wg.ops = make(chan int)) Because of line 14, you could create >> more than one channel here, and different go routines will read a different >> wg.ops, so your WaitGroup won't work. You also have a data race in >> assigning wg.ops because wg.ops isn't an atomic type. >> >> Line 41: op, ok := <-wg.ops . You could be waiting forever because of >> leaked channel in line 17 >> >> It seems like your code works for a simple test, but if you really hammer >> on this thing, it's going to fail. >> >> -- Marcin >> >> >> On Sun, May 5, 2019 at 8:33 AM Louki Sumirniy <louki.sumi...@gmail.com> >> wrote: >> >>> just had to drop an update, I further improved it, now when it stops it >>> resets itself and you can use Add again, I removed the unnecessary >>> positive/negative checks and condensed the Add and Done function into an >>> Add that can take a negative (tried to think of a better word but Modify >>> and Change... just didn't quite fit) >>> >>> https://play.golang.org/p/cO3sV1w9-Re >>> >>> One could add back separate Add and Done functions, even conveniences >>> that just inc 1 dec 1 for each but I don't see the point in that. It just >>> simply allows you to make Wait block while the count is above zero, which >>> is all you need to prevent goroutine leaks. >>> >>> On Sunday, 5 May 2019 17:18:52 UTC+2, Louki Sumirniy wrote: >>>> >>>> I figured out that the API of my design had a flaw separating starting >>>> the goroutine and adding a new item, so as you can see in this code, I >>>> have >>>> merged them and notice that there is basically no extraneous 'helper' >>>> functions also: >>>> >>>> https://play.golang.org/p/hR1sTOAwkOm >>>> >>>> The flaw I made relates to API abuse - the contract of the library is >>>> quite simply to concurrently keep track of the adjacent 'go' calls >>>> starting >>>> goroutines, which doesn't apply until there is an increment. So you should >>>> not be able to abuse this one the way you did the last. >>>> >>>> I'm pretty sure at somewhere between 50 and 100 worker routines the >>>> lack of complex extra code makes this a better implementation. Probably >>>> the >>>> original is fine for smaller worker pools. >>>> >>>> On Sunday, 5 May 2019 13:01:58 UTC+2, Jan Mercl wrote: >>>>> >>>>> On Sun, May 5, 2019 at 12:45 PM Louki Sumirniy >>>>> <louki.sumi...@gmail.com> wrote: >>>>> > >>>>> > https://play.golang.org/p/5KwJQcTsUPg >>>>> > >>>>> > I fixed it. >>>>> >>>>> Not really. You've introduced a data race. >>>>> >>>>> jnml@4670:~/src/tmp> cat main.go >>>>> package main >>>>> >>>>> type WaitGroup struct { >>>>> workers int >>>>> ops chan int >>>>> } >>>>> >>>>> func New() *WaitGroup { >>>>> wg := new(WaitGroup) >>>>> return wg >>>>> } >>>>> >>>>> func startWait(wg *WaitGroup) { >>>>> wg.ops = make(chan int) >>>>> >>>>> go func() { >>>>> done := false >>>>> for !done { >>>>> select { >>>>> case op := <-wg.ops: >>>>> wg.workers += op >>>>> if wg.workers < 1 { >>>>> done = true >>>>> close(wg.ops) >>>>> } >>>>> } >>>>> } >>>>> }() >>>>> } >>>>> >>>>> // Add adds a non-negative number >>>>> func (wg *WaitGroup) Add(delta int) { >>>>> if delta < 0 { >>>>> return >>>>> } >>>>> if wg.ops == nil { >>>>> startWait(wg) >>>>> } >>>>> wg.ops <- delta >>>>> } >>>>> >>>>> // Done subtracts a non-negative value from the workers count >>>>> func (wg *WaitGroup) Done(delta int) { >>>>> if delta < 0 { >>>>> return >>>>> } >>>>> wg.ops <- -delta >>>>> } >>>>> >>>>> // Wait blocks until the waitgroup decrements to zero >>>>> func (wg *WaitGroup) Wait() { >>>>> for { >>>>> if wg.workers < 1 { >>>>> break >>>>> } >>>>> op, ok := <-wg.ops >>>>> if !ok { >>>>> break >>>>> } else { >>>>> wg.ops <- op >>>>> } >>>>> } >>>>> } >>>>> >>>>> var wg = New() >>>>> >>>>> func main() { >>>>> for i := 0; i < 2; i++ { >>>>> wg.Add(1) >>>>> go f() >>>>> } >>>>> wg.Wait() >>>>> } >>>>> >>>>> func f() { >>>>> defer wg.Done(1) >>>>> >>>>> for i := 0; i < 2; i++ { >>>>> wg.Add(1) >>>>> go g() >>>>> } >>>>> } >>>>> >>>>> func g() { wg.Done(1) } >>>>> jnml@4670:~/src/tmp> go run -race main.go >>>>> ================== >>>>> WARNING: DATA RACE >>>>> Read at 0x00c00008a000 by main goroutine: >>>>> main.(*WaitGroup).Wait() >>>>> /home/jnml/src/tmp/main.go:53 +0x6f >>>>> main.main() >>>>> /home/jnml/src/tmp/main.go:72 +0x104 >>>>> >>>>> Previous write at 0x00c00008a000 by goroutine 5: >>>>> main.startWait.func1() >>>>> /home/jnml/src/tmp/main.go:21 +0xbb >>>>> >>>>> Goroutine 5 (running) created at: >>>>> main.startWait() >>>>> /home/jnml/src/tmp/main.go:16 +0x9e >>>>> main.main() >>>>> /home/jnml/src/tmp/main.go:37 +0xdf >>>>> ================== >>>>> Found 1 data race(s) >>>>> exit status 66 >>>>> jnml@4670:~/src/tmp> >>>>> >>>> -- >>> 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 golan...@googlegroups.com. >>> For more options, visit https://groups.google.com/d/optout. >>> >> -- 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. For more options, visit https://groups.google.com/d/optout.