Search code examples
gogoroutine

Selecting inside range over go channel


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

Solution

    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.