在转到通道上选择内部范围

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
  1. Being in a range or not doesn't have any impact on what select is doing here.

  2. 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
}