更好的方式来编写这段代码吗?

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

Better go-idiomatic way of writing this code?

问题

使用go语言玩耍时,我写了这段代码:

package main

import "fmt"

const N = 10

func main() {
    ch := make(chan int, N)
    done := make(chan bool)

    for i := 0; i < N; i++ {
        go (func(n int, ch chan int, done chan bool) {
            for i := 0; i < N; i++ {
                ch <- n*N + i
            }
            done <- true
        })(i, ch, done)
    }

    numDone := 0
    for numDone < N {
        select {
        case i := <-ch:
            fmt.Println(i)
        case <-done:
            numDone++
        }
    }

    for {
        select {
        case i := <-ch:
            fmt.Println(i)
        default:
            return
        }
    }
}

基本上,我有N个通道在做一些工作,并在同一个通道上报告结果 - 我想知道所有通道何时完成。所以我有这个另外的done通道,每个工作协程都会发送一条消息(消息无关紧要),这会导致主协程将该线程视为已完成。当计数达到N时,我们实际上完成了。

这段代码是否“好”?是否有更符合go语言习惯的方法来实现这个功能?

编辑:为了澄清一下,我对此表示怀疑,因为done通道似乎在做通道关闭的工作,但是由于所有协程共享同一个通道,我实际上无法在任何协程中关闭通道。所以我使用done来模拟一种执行某种“缓冲关闭”的通道。

编辑2:原始代码实际上并没有正常工作,因为有时从协程发送的done信号会在刚刚放入ch的整数之前被读取。需要一个“清理”循环。

英文:

Playing around with go, I threw together this code:

package main

import &quot;fmt&quot;

const N = 10

func main() {
	ch := make(chan int, N)
	done := make(chan bool)

	for i := 0; i &lt; N; i++ {
		go (func(n int, ch chan int, done chan bool) {
			for i := 0; i &lt; N; i++ {
				ch &lt;- n*N + i
			}
			done &lt;- true
		})(i, ch, done)
	}

	numDone := 0
	for numDone &lt; N {
		select {
		case i := &lt;-ch:
			fmt.Println(i)
		case &lt;-done:
			numDone++
		}
	}

	for {
		select {
		case i := &lt;-ch:
			fmt.Println(i)
		default:
			return
		}
	}
}

Basically I have N channels doing some work and reporting it on the same channel -- I want to know when all the channels are done. So I have this other done channel that each worker goroutine sends a message on (message doesn't matter), and this causes main to count that thread as done. When the count gets to N, we're actually done.

Is this "good" go? Is there a more go-idiomatic way of doing this?

edit: To clarify a bit, I'm doubtful because the done channel seems to be doing a job that channel closing seems to be for, but of course I can't actually close the channel in any goroutine because all the routines share the same channel. So I'm using done to simulate a channel that does some kind of "buffered closing".

edit2: Original code wasn't really working since sometimes the done signal from a routine was read before the int it just put on ch. Needs a "cleanup" loop.

答案1

得分: 10

这里有一个关于sync.WaitGroup的习惯用法供您学习

playground链接

package main

import (
	"fmt"
	"sync"
)

const N = 10

func main() {
	ch := make(chan int, N)
	var wg sync.WaitGroup
	for i := 0; i < N; i++ {
		wg.Add(1)
		go func(n int) {
			defer wg.Done()
			for i := 0; i < N; i++ {
				ch <- n*N + i
			}
		}(i)
	}
	go func() {
		wg.Wait()
		close(ch)
	}()
	for i := range ch {
		fmt.Println(i)
	}
}

请注意两个go例程定义中闭包的使用,并注意第二个go语句等待所有例程完成,然后关闭通道,以便可以使用range

英文:

Here is an idiomatic use of sync.WaitGroup for you to study

(playground link)

package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
)

const N = 10

func main() {
	ch := make(chan int, N)
	var wg sync.WaitGroup
	for i := 0; i &lt; N; i++ {
		wg.Add(1)
		go func(n int) {
			defer wg.Done()
			for i := 0; i &lt; N; i++ {
				ch &lt;- n*N + i
			}
		}(i)
	}
	go func() {
		wg.Wait()
		close(ch)
	}()
	for i := range ch {
		fmt.Println(i)
	}
}

Note the use of closures in the two go routine definitions and note the second go statement to wait for all the routines to finish, then close the channel, so range can be used.

答案2

得分: 2

看起来你想要一个sync.WaitGroup (http://golang.org/pkg/sync/#WaitGroup)。

英文:

looks like you want a sync.WaitGroup (http://golang.org/pkg/sync/#WaitGroup)

答案3

得分: 1

只需使用WaitGroup!它们是内置的原语,基本上允许您等待不同goroutine中的任务完成。

http://golang.org/pkg/sync/#WaitGroup

至于您的疑问,可以这样考虑:通过关闭通道来完成(永久性完成)和通过完成工作来完成(临时完成)是不同的。

英文:

Just use a WaitGroup! They are the built-in primitive that essentially let you wait for stuff in different goroutines to finish up.

http://golang.org/pkg/sync/#WaitGroup

As for your doubts, The way to thing about is that being done by closing a channel (done permanently) and being done with work (temporarily) are different.

答案4

得分: 1

在第一次近似中,代码对我来说看起来还可以。

关于细节方面,'ch' 应该被缓冲。另外,'done' 通道的 goroutine "accounting" 可能可以用 sync.WaitGroup 来替代。

英文:

In the first approximation the code seems more or less okay to me.

Wrt the details, the 'ch' should be buffered. Also the 'done' channel goroutine "accounting" might be possibly replaced with sync.WaitGroup.

答案5

得分: 0

如果您正在迭代通过goroutine生成的值,您可以直接迭代通信通道:

for value := range ch {
   println(value)
}

唯一需要的是,通道ch稍后关闭,否则循环将永远等待新值。

当与sync.WaitGroup结合使用时,这将有效地替换您的for numDone &lt; N

英文:

If you're iterating over values generated from goroutines, you can iterate directly over the
communication channel:

for value := range ch {
   println(value)
}

The only thing necessary for this is, that the channel ch is closed later on, or else the
loop would wait for new values forever.

This would effectively replace your for numDone &lt; N when used in combination with sync.WaitGroup.

答案6

得分: 0

我在处理我的一些代码中遇到了相同的问题,并发现这个是一个非常合适的解决方案。

这个答案提供了Go语言处理多个goroutine同时发送到一个通道的惯用方法。

英文:

I was dealing with the same issue in some code of mine and found this to be a more than adequate solution.

The answer provides Go's idiom for handling multiple goroutines all sending across a single channel.

huangapple
  • 本文由 发表于 2013年4月16日 00:40:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/16020406.html
匿名

发表评论

匿名网友

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

确定