Issue
Like many go programmers, have so far avoided doing anything significant with channels so this apparently simple scenario has got me stumped!
I want multiple goroutines to send results checked by a single parent. If any send an error, the parent should signal for them all to stop. The parent should read the results non-sequentially.
This code works provided one of the goroutines does send an error i.e. if you comment in the 11
inside nums otherwise we just get stuck in the for loop forever.
func main() {
type Result struct {
Error error
Response int
}
checkStatus := func(done <-chan interface{}, ns []int) <-chan Result {
results := make(chan Result)
go func() {
defer close(results)
for _, n := range ns {
result := Result{Response: n}
if n == 11 {
result.Error = fmt.Errorf("problem...\n")
}
select {
case <-done:
return
case results <- result:
}
}
}()
return results
}
done := make(chan interface{})
defer close(done)
nums := []int{1, 2, 3, 4, 5 /*11,*/, 6, 7, 8, 9, 10}
c1 := checkStatus(done, nums[:5])
c2 := checkStatus(done, nums[5:])
for {
var r Result
select {
case r = <-c1:
if r.Error != nil {
fmt.Printf("error1: %v", r.Error)
return
}
fmt.Printf("Response1: %v\n", r.Response)
case r = <-c2:
if r.Error != nil {
fmt.Printf("error2: %v", r.Error)
return
}
fmt.Printf("Response2: %v\n", r.Response)
}
}
}
The only way I can see to fix it is the change the for
loop so it reads from c1
and c2
but I can’t see a way to do this non-sequentially?
https://go.dev/play/p/7dRPMDn1Za2
Solution
You’re reading from closed channels and they always return a zero value. What you can do is to turn off a select case by using a comma ok idiom while reading from a channel and then assign the channel to nil. If the other one is nil as well then return.
case
with a nil channel never runs.
Just by extending your code (similarly for the case with c2
):
case r, ok := <-c1:
if !ok {
c1 = nil
if c2 == nil {
return
}
continue
}
if r.Error != nil {
fmt.Printf("error1: %v", r.Error)
return
}
fmt.Printf("Response1: %v\n", r.Response)
But I’d rather try to refactor the whole implementation.
What you can consider is using sync.WaitGroup
from the sync
pkg or errgroup.Group
from the errgroup
pkg.
Answered By – Erni27
Answer Checked By – Cary Denson (GoLangFix Admin)