I need to run a function multiple times in parallel.
If even once the functions returns true
(sends true
on the channel) then the final result should be true
.
How do I achieve this using goroutines and channels?
// Some performance intensive function
func foo(i int, c chan bool) {
// do some processing and return either true or false
c <- true // or false
}
func main() {
flg := false
ch := make(chan bool)
for i := 0; i < 10; i++ {
go foo(i, ch)
}
// If even once foo() returned true then val should be true
flg = flg || <-ch
}
You only receive one value from the channel (which will be the value sent by one of the foo()
calls, unpredictable which of the many), but you want to receive all.
So use a for
loop to receive as many values as you send (sent) on it:
for i := 0; i < 10; i++ {
flg = flg || <-ch
}
Although in your case it would be enough to loop until one true
value is received, as that will determine the final value of flg
, but it is still recommended to receive all values else the remaining goroutines will be blocked (as ch
is an unbuffered channel). In this example it does not matter, but in a "real-life" application it would cause goroutines to stuck forever (memory-leak).
If you don't want to wait for all foo()
calls to complete and return as soon as possible (as soon as one true
value is encountered), an option is to make ch
buffered, so all goroutines can send values on it without getting blocked. And this way you are not required to receive (and thus wait for) all the foo()
calls to complete:
ch := make(chan bool, 10)
for i := 0; i < 10; i++ {
go foo(i, ch)
}
flg := false
for i := 0; i < 10; i++ {
if <-ch {
flg = true
break
}
}
Choosing this approach, you should provide means to cancel goroutines whose work is no longer needed to avoid unnecessary CPU (and memory) usage. context.Context
is such a mean, read more about it here: Close multiple goroutine if an error occurs in one in go.
You may start reading from the channel ch
and set flg
to true
once you get true result. Like this:
//flg = flg || <- ch
for res := range ch {
if res {
flg = true
}
}
This way works but has one serious drawback - for
loop waits new values from channel infinitely. Idiomatic way to stop the loop is to close the channel. You may do it so: run a separate goroutine which will wait until all goroutines exit. Go provides a very convenient tools to do it - sync.WaitGroup
.
Define it in a global scope so every goroutine can access to it:
var (
wg sync.WaitGroup
)
Then every time you launch goroutine you add one more goroutine to wait group:
for i := 0; i < 10; i++ {
wg.Add(1) // here
go foo(i, ch)
}
When goroutine finishes it calls wg.Done
method to mark it.
func foo(i int, c chan bool) {
//do some processing and return either true or false
c <- true //or false
wg.Done() // here
}
Then sepatate goroutine waits until all foo goroutines exit and closes the channel. wg.Wait
blocks until all are done:
go func() {
wg.Wait()
close(ch)
}()
All together: https://play.golang.org/p/8qiuA29-jv