在 go 通道上选择内部范围

Selecting inside range over go channel

我正在关注 this post 以并行化我的应用程序。我需要定制这段代码:

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
}

我不完全理解 case out <- n * n: 行。我可以看到它是说如果 n 有一个值,然后将它平方并发送到通道,但我不明白为什么。 select 是否只接受第一个 true 案例?能否改写:

for n := range in {
    select {
    case n:
       out <- n * n
    case <-done:
       return
    }
}

无论如何,我需要用函数调用替换行 case out <- n * n:。我已将其更改为以下内容:

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

看起来这会编译(我还不能编译它),但是因为调试并行代码并不简单我想检查使用 case url 是否是 [=36= 的正确方法] 在 range 的一个频道上。这样对吗?

更新

好的,我已经删除了代码中的剩余问题,现在当我尝试编译时,我收到了错误消息:

url evaluated but not used
select case must be receive, send or assign recv

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.

这是不正确的。 case out <- n * n 检查 out 是否准备好读取,如果准备就绪则发送 n * nout。除非done也准备好了。

select 当您有多个频道可以通话时使用。无论哪个通道准备就绪,它都会执行该操作。如果有多个通道就绪,它会 select 随机一个。

select {
    case out <- n * n:
    case <-done:
        return
    }
}

这将 select 超过 outdone。如果任何一个准备好继续,即。 out 已准备好阅读或 done 有内容可读,它将选择其中一种情况。顺序是随机的,因此即使 done.

有内容需要读取,也可以发送更多 out

此模式用于关闭无限协程。如果你停止从它的输出通道读取,它就不会再做任何工作,但它会在内存中徘徊。因此,通过将值传递给 done,您可以告诉 goroutine 关闭。


UPDATE:在您原来的情况下,goroutine 在输入通道上循环并发送输出,done是不必要的并发症。输入通道关闭后,函数将 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)
}

如果它只是吐出一组不断增加的正方形,那么 done 在无限循环中是必要的。

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
}
  1. 是否在 range 中对 select 在这里所做的事情没有任何影响。

  2. 不,select 不采用第一个真表达式...它根本不采用表达式。唯一可以作为表达式的情况出现的是通道发送、通道接收和右侧带有通道接收的分配。

    select {
    case out <- n * n:
    case <-done:
        return
    }
    

说 "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."(参见规范中的 Select Statements)。

如果您要发送的值需要计算(并且它太复杂而无法放在通道发送的右侧),只需在 select 之前执行即可。规范清楚地表明,select 中发送语句中的所有表达式都是提前计算的,因此不会丢失任何内容。