concurrent.go:
package main
import (
"fmt"
"sync"
)
// JOBS represents the number of jobs workers do
const JOBS = 2
// WORKERS represents the number of workers
const WORKERS = 5
func work(in <-chan int, out chan<- int, wg *sync.WaitGroup) {
for n := range in {
out <- n * n
}
wg.Done()
}
var wg sync.WaitGroup
func main() {
in := make(chan int, JOBS)
out := make(chan int, JOBS)
for w := 1; w <= WORKERS; w++ {
wg.Add(1)
go work(in, out, &wg)
}
for j := 1; j <= JOBS; j++ {
in <- j
}
close(in)
wg.Wait()
close(out)
for r := range out {
fmt.Println("result:", r)
}
// This is a solution but I want to do it with `range out`
// and also without WaitGroups
// for r := 1; r <= JOBS; r++ {
// fmt.Println("result:", <-out)
// }
}
Example is here on goplay.
This is a proof of example syncing without waitgroup
.
package main
import (
"fmt"
)
// number of jobs workers do
const JOBS = 10
// number of workers
const WORKERS = 2
func work(in <-chan int, out chan<- int, done chan<- bool) {
for n := range in {
out <- n * n
}
done <- true
}
func main() {
in := make(chan int, JOBS)
out := make(chan int, JOBS)
done := make(chan bool, WORKERS)
// launch workers
for w := 1; w <= WORKERS; w++ {
go work(in, out, done)
}
// give jobs to workers
for j := 1; j <= JOBS; j++ {
in <- j
}
close(in)
// list the results
go func() {
i := 0
for r := range out {
fmt.Println("result:", r)
// when all jobs completed mark as done
if i++; i == JOBS {
done <- true
}
}
}()
// wait for all goroutines to keep up
// WORKERS + RESULT go routines
for i := 0; i < WORKERS + 1; i++ {
<- done
}
// prevent leaking chans
close(out)
}
Goroutines run concurrently and independently. Spec: Go statements:
A "go" statement starts the execution of a function call as an independent concurrent thread of control, or goroutine, within the same address space.
If you want to use for range
to receive values from the out
channel, that means the out
channel can only be closed once all goroutines are done sending on it.
Since goroutines run concurrently and independently, without synchronization you can't have this.
Using WaitGroup
is one mean, one way to do it (to ensure we wait all goroutines to do their job before closing out
).
Your commented code is another way of that: the commented code receives exactly as many values from the channel as many the goroutines ought to send on it, which is only possible if all goroutines do send their values. The synchronization are the send statements and receive operations.
Notes:
Usually receiving results from the channel is done asynchronously, in a dedicated goroutine, or using even multiple goroutines. Doing so you are not required to use channels with buffers capable of buffering all the results. You will still need synchronization to wait for all workers to finish their job, you can't avoid this due to the concurrent and independent nature of gorutine scheduling and execution.