在Go语言中,通过Go通道进行范围选择。

huangapple go评论147阅读模式
英文:

Selecting inside range over go channel

问题

我正在遵循这篇文章来并行化我的应用程序。我需要修改这段代码:

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 的 case 吗?它可以重写为:

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 来在 range 中选择通道是否正确。这样做对吗?

更新

好的,我已经解决了代码中剩下的问题,现在当我尝试编译时,我得到以下错误信息:

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

I'm following this post to parallelise my app. I need to tailor this code:

func sq(done &lt;-chan struct{}, in &lt;-chan int) &lt;-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            select {
            case out &lt;- n * n:
            case &lt;-done:
                return
            }
        }
    }()
    return out
}

I don't fully understand the line case out &lt;- 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 &lt;- n * n
    case &lt;-done:
       return
    }
}

Anyway, I need to replace the line case out &lt;- 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(&quot;Got error: &quot;, err)
			out &lt;- data
		case &lt;-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

得分: 1

  1. 在这里,是否在range中并不影响select的操作。

  2. 不,select并不选择第一个为真的表达式...它根本不接受表达式。作为表达式的情况只能是通道发送、通道接收和右侧带有通道接收的赋值。

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

上述代码的意思是:“如果在out上发送是可能的(即它有剩余容量或者有活跃的读取器),那么发送值n * n并继续。如果从done接收是可能的,那么从函数中返回。如果两者都可能,随机选择一个并执行。如果两者都不可能,等待直到其中一个变为可能。”(参见Select Statements 规范)。如果要发送的值需要计算(且计算过程太复杂无法放在通道发送的右侧),只需在select之前进行计算即可。规范明确指出,在select中的所有发送语句的表达式都会提前计算,因此不会有任何损失。

英文:
  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 &lt;- n * n:
     case &lt;-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.

答案2

得分: 0

我不完全理解case out <- n * n这一行的含义。我可以看到它的意思是,如果n有一个值,那么将其平方并发送到通道中,但我不明白为什么要这样做。

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

select用于在多个通道之间进行选择。无论哪个通道准备好,它都会执行相应的case。如果多个通道都准备好,它会随机选择一个。

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

这将在outdone之间进行选择。如果其中一个准备好继续执行,即out准备好读取或者从done中有东西可读取,它将选择其中一个case。顺序是随机的,因此即使从done中有东西可读取,仍有可能向out发送更多的值。

这种模式用于关闭无限的goroutine。如果停止从其输出通道读取,它将不再执行任何工作,但它将在内存中保留。因此,通过向done传递一个值,您可以告诉goroutine关闭。


更新:在您的原始情况中,goroutine循环遍历输入通道并发送输出时,done是一个不必要的复杂性。一旦关闭输入通道,函数将返回。

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)
    }

    // sq()中的循环将退出,并且goroutine将返回。
    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)
    }

    // goroutine中的select将能够从done中读取(out的缓冲区已满)并返回。
    done <- true
}
英文:

> I don't fully understand the line case out &lt;- 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 &lt;- 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 &lt;- n * n:
    case &lt;-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 &lt;-chan int) &lt;-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            out &lt;- n * n
        }
    }()
    return out
}

func main() {
    in := make(chan int)
    out := sq(in)
    for _,i := range []int{1,2,3,4} {
        in &lt;- i
        fmt.Println(&lt;-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) &lt;-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        n := 0
        for {
            select {
                case &lt;-done:
                    return
                case out&lt;-n*n:
                    n++
            }
        }
    }()
    return out
}

func main() {
    done := make(chan bool)
    out := sq(done)
    for range []int{1,2,3,4} {
        fmt.Println(&lt;-out)
    }

    // The switch in the goroutine will be able to read
    // from done (out&#39;s buffer being already full) and return.
    done &lt;- true
}

huangapple
  • 本文由 发表于 2017年9月10日 01:10:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/46133127.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定