英文:
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 <-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
得分: 1
-
在这里,是否在
range
中并不影响select
的操作。 -
不,
select
并不选择第一个为真的表达式...它根本不接受表达式。作为表达式的情况只能是通道发送、通道接收和右侧带有通道接收的赋值。select { case out <- n * n: case <-done: return }
上述代码的意思是:“如果在out
上发送是可能的(即它有剩余容量或者有活跃的读取器),那么发送值n * n
并继续。如果从done
接收是可能的,那么从函数中返回。如果两者都可能,随机选择一个并执行。如果两者都不可能,等待直到其中一个变为可能。”(参见Select Statements 规范)。如果要发送的值需要计算(且计算过程太复杂无法放在通道发送的右侧),只需在select
之前进行计算即可。规范明确指出,在select
中的所有发送语句的表达式都会提前计算,因此不会有任何损失。
英文:
-
Being in a
range
or not doesn't have any impact on whatselect
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.
答案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
}
这将在out
和done
之间进行选择。如果其中一个准备好继续执行,即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 <- 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
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论