I'm following this post to parallelise my app. I need to tailor this code:
func sq(done <-chan struct{}, in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
select {
case out <- n * n:
case <-done:
return
}
}
}()
return out
}
I don't fully understand the line case out <- n * n:
. I can see it's saying that if there's a value for n
, then square it and send it down the channel, but I don't understand why. Does select
just take the first true
case? Could it be rewritten:
for n := range in {
select {
case n:
out <- n * n
case <-done:
return
}
}
Anyway, I need to replace the line case out <- n * n:
with a function call. I've changed it to the following:
out := make(chan structs.Ticket)
go func() {
defer close(out)
for url := range inputChannel {
select {
case url:
data, err := GetData(url)
fmt.Println("Got error: ", err)
out <- data
case <-done:
return
}
}
}()
return out
It looks like this will compile (I can't compile it yet), but because it's not simple to debug parallel code I wanted to check that using case url
was the right way to select on a channel in a range
. Is this right?
Update
OK I've removed the remaining issues with my code, and now when I try to compile I get the error messages:
url evaluated but not used
select case must be receive, send or assign recv
Being in a range
or not doesn't have any impact on what select
is doing here.
No, select
doesn't take the first true expression... it doesn't take expressions at all. The only things that can appear as the cases of an expression are channel sends, channel receives, and assignments with channel receives on their right side.
select {
case out <- n * n:
case <-done:
return
}
says "if sending on out
is possible (i.e. it has remaining capacity or an active reader), then send the value n * n
to it and continue. If receiving from done
is possible, return from the function. If both are possible, choose one at random and do it. If neither is possible, wait until one of them becomes possible." (See Select Statements in the spec).
If the value you want to send needs to be computed (and it's too complex to put on the right hand side of the channel send), simply do it before the select
. The spec makes it clear that all of the expressions in send statements in a select are computed ahead of time anyway, so nothing is lost.
I don't fully understand the line
case out <- n * n
:. I can see it's saying that if there's a value for n, then square it and send it down the channel, but I don't understand why.
That's not correct. case out <- n * n
checks to see if out
is ready to read, and sends n * n
to out
if it is. Unless done
is also ready.
select
is used when you have multiple channels to talk to. Whichever channel is ready, it will do that case. If multiple channels are ready, it will select one at random.
select {
case out <- n * n:
case <-done:
return
}
}
This will select over out
and done
. If either is ready to proceed, ie. out
is ready to read or there's something to read from done
, it will pick one of those cases. The order is random, so it is possible to send more down out
even if there's something to be read from done
.
This pattern is used to shut down infinite goroutines. If you stop reading from its output channel, it won't do any more work, but it will hang around in memory. So by passing a value to done
you can tell the goroutine to shut down.
UPDATE: In your original case, where the goroutine is looping over an input channel and sending output, done
is an unnecessary complication. Once the input channel is closed, the function will return.
func sq(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
out <- n * n
}
}()
return out
}
func main() {
in := make(chan int)
out := sq(in)
for _,i := range []int{1,2,3,4} {
in <- i
fmt.Println(<-out)
}
// The `range` inside the goroutine from sq() will exit,
// and the goroutine will return.
close(in)
}
If it just spat out an ever increasing set of squares, then done
would be necessary inside an infinite loop.
func sq(done chan bool) <-chan int {
out := make(chan int)
go func() {
defer close(out)
n := 0
for {
select {
case <-done:
return
case out<-n*n:
n++
}
}
}()
return out
}
func main() {
done := make(chan bool)
out := sq(done)
for range []int{1,2,3,4} {
fmt.Println(<-out)
}
// The switch in the goroutine will be able to read
// from done (out's buffer being already full) and return.
done <- true
}