本文介绍了关闭“工作者”在缓冲区为空之后执行程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 29岁程序员,3月因学历无情被辞! 我想让我的日常工作者( ProcessToDo()在下面的代码)等待所有排队的工作在关闭前处理。 工作例程有一个待办通道(缓冲),通过它工作发送给它。它有一个完成通道来告诉它开始关机。文档说如果多个选择被满足,通道上的选择将选择伪随机值...这意味着在所有缓冲的工作完成之前触发关闭(返回)。 在下面的代码示例中,我要打印所有20条消息... code> package main import(timefmt) func ProcessToDo (done chan struct {},todo chan string){ for { select { case work,ok:=< -todo: if!ok { fmt.Printf(Shutdown down ProcessToDo - todo channel closed!\\\) return } fmt.Printf(todo:%q\\\,work) time.Sleep(100 * time.Millisecond) case _,ok:=< -done: if ok { fmt.Printf(正在关闭ProcessToDo - 完成消息收到!\\\)} else { fmt.Printf(关闭ProcessToDo - 完成通道关闭!\\\)} 关闭b $ b return } } } func main(){ done:= make(chan struct {}) todo:= make(chan string,100) go ProcessToDo(done,todo) for i:= 0; i< 20; i ++ { todo< - fmt.Sprintf(Message%02d,i)} fmt.Println(*** all messages queued *** ) time.Sleep(1 * time.Second) close(done) time.Sleep(4 * time.Second)} 解决方案 完成完全不必要,因为您可以通过关闭 todo 通道本身来通知关机。 并使用 您应该拥有一个 done 通道,但只有这样,goroutine本身可以表示它完成工作,所以主goroutine可以继续或退出。 这个变体等同于你的,更简单,不需要 time.Sleep()调用等待其他goroutine(这将是太错误和不确定性)。请在 Go Playground 上尝试: func ProcessToDo(done chan struct {},todo chan string){ for work:= range todo { fmt.Printf(todo:%q \ n,work) time.Sleep(100 * time.Millisecond)} fmt.Printf(关闭ProcessToDo - todo通道已关闭!\\\) done } func main(){ done:= make(chan struct {}) todo:= make(chan string,100) go ProcessToDo(done,todo) for i:= 0; i< 20; i ++ { todo< - fmt.Sprintf(Message%02d,i)} fmt.Println(*** all messages queued *** ) close(todo)< -done //等待另一个goroutine完成所有作业} 还要注意,worker goroutines应该使用 defer 来表示完成,所以主goroutine不会卡住等待worker返回以某种意想不到的方式,或恐慌。所以应该这样开始: defer func(){ done< - struct {} {} //我们处理所有作业的信号}() href =http://golang.org/pkg/sync/#WaitGroup =nofollow> sync.WaitGroup 同步主goroutine到工人(等待它)。事实上,如果你打算使用多个工作者goroutine,那么比从完成通道读取多个值更清晰。也可以用 WaitGroup 来表示完成的信号更简单,因为它有一个 Done() 方法(这是一个函数调用),因此您不需要一个匿名函数: defer wg.Done() 有关 WaitGroup 的完整示例,请参见 JimB的anwser 。 p> 如果要使用多个工作程序goroutine,使用范围的也是惯用的:通道被同步,需要任何额外的代码来同步访问 todo 通道或从其接收的作业。如果您关闭 main()中的 todo 通道,那么将正确地指示所有工作程序goroutine。 现在,使用 WaitGroup 的变体使主要的goroutine等待工人(JimB的答案):如果你想要超过1个工人goroutine怎么办? 您需要在代码中添加/更改的唯一方法是:真正开始多个方法: / p> for i:= 0; i< 10; i ++ { wg.Add(1) go ProcessToDo(todo)} 不改变任何东西,你现在有一个正确的并发应用程序接收和处理你的工作使用10个并发goroutine。我们没有使用任何丑陋的 time.Sleep()(我们使用一个,但只模拟慢处理,不等待其他goroutines) t需要任何额外的同步。 I want my go routine worker (ProcessToDo() in the code below) to wait until all "queued" work is processed before shutting down.The worker routine has a "to do" channel (buffered), through which work is sent to it. And it has a "done" channel to tell it to start shutdown. The documentation says that the select on the channels will pick a "pseudo-random value" if more than one of the selects are met... which means the shutdown (return) is being triggered before all the buffered work is completed.In the code sample below, I want all 20 messages to print... package mainimport ( "time" "fmt")func ProcessToDo(done chan struct{}, todo chan string) { for { select { case work, ok := <-todo: if !ok { fmt.Printf("Shutting down ProcessToDo - todo channel closed!\n") return } fmt.Printf("todo: %q\n", work) time.Sleep(100 * time.Millisecond) case _, ok := <-done: if ok { fmt.Printf("Shutting down ProcessToDo - done message received!\n") } else { fmt.Printf("Shutting down ProcessToDo - done channel closed!\n") } close(todo) return } }}func main() { done := make(chan struct{}) todo := make(chan string, 100) go ProcessToDo(done, todo) for i := 0; i < 20; i++ { todo <- fmt.Sprintf("Message %02d", i) } fmt.Println("*** all messages queued ***") time.Sleep(1 * time.Second) close(done) time.Sleep(4 * time.Second)} 解决方案 done channel in your case is completely unnecessary as you can signal the shutdown by closing the todo channel itself.And use the for range on the channel which will iterate until the channel is closed and its buffer is empty.You should have a done channel, but only so that the goroutine itself can signal that it finished work and so the main goroutine can continue or exit.This variant is equivalent to yours, is much simpler and does not require time.Sleep() calls to wait other goroutines (which would be too erroneous and undeterministic anyway). Try it on the Go Playground:func ProcessToDo(done chan struct{}, todo chan string) { for work := range todo { fmt.Printf("todo: %q\n", work) time.Sleep(100 * time.Millisecond) } fmt.Printf("Shutting down ProcessToDo - todo channel closed!\n") done <- struct{}{} // Signal that we processed all jobs}func main() { done := make(chan struct{}) todo := make(chan string, 100) go ProcessToDo(done, todo) for i := 0; i < 20; i++ { todo <- fmt.Sprintf("Message %02d", i) } fmt.Println("*** all messages queued ***") close(todo) <-done // Wait until the other goroutine finishes all jobs}Also note that worker goroutines should signal completion using defer so the main goroutine won't get stuck waiting for the worker if it returns in some unexpected way, or panics. So it should rather start like this:defer func() { done <- struct{}{} // Signal that we processed all jobs}()You can also use sync.WaitGroup to sync the main goroutine to the worker (to wait it up). In fact if you plan to use multiple worker goroutines, that is cleaner than to read multiple values from the done channel. Also it's simpler to signal the completion with WaitGroup as it has a Done() method (which is a function call) so you don't need an anonymous function:defer wg.Done()See JimB's anwser for the complete example with WaitGroup.Using the for range is also idiomatic if you want to use multiple worker goroutines: channels are synchronized so you don't need any extra code that would synchronize access to the todo channel or the jobs received from it. And if you close the todo channel in the main(), that will properly signal all worker goroutines. But of course all queued jobs will be received and processed exactly once.Now taking the variant that uses WaitGroup to make the main goroutine to wait for the worker (JimB's answer): What if you want more than 1 worker goroutine; to process your jobs concurrently (and most likely parallel)?The only thing you need to add / change in your code is this: to really start multiple of them:for i := 0; i < 10; i++ { wg.Add(1) go ProcessToDo(todo)}Without changing anything else, you now have a correct, concurrent application which receives and processes your jobs using 10 concurrent goroutines. And we haven't used any "ugly" time.Sleep() (we used one but only to simulate slow processing, not to wait other goroutines), and you don't need any extra synchronization. 这篇关于关闭“工作者”在缓冲区为空之后执行程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 上岸,阿里云!
08-07 05:48