我希望我的常规工作程序(下面的代码中为ProcessToDo()
)等待,直到关闭之前处理所有“排队”工作。
工作程序具有“待办事项”通道(缓冲),通过该通道向其发送工作。它有一个“完成”通道告诉它开始关机。文档说如果满足多个选择,通道上的选择将选择“伪随机值”...这意味着在完成所有缓冲工作之前触发关闭(返回)。
在下面的代码示例中,我希望打印所有20条消息...
package main
import (
"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)
}
答案 0 :(得分:6)
scala> val m = collection.immutable.TreeMap(List.fill(5)(f1 -> f2) : _*)
m: scala.collection.immutable.TreeMap[String,String] =
Map(3cieU -> iy0KV, 8oUb1 -> YY6NC, 95ol4 -> Sf9qp, GhXWX -> 8U8wt, ZD8Px -> STMOC)
频道完全没必要,因为您可以通过关闭done
频道本身来发出关机信号。
并在通道上使用todo
,该通道将迭代直到通道关闭且其缓冲区为空。
你应该拥有一个for range
频道,但只是为了让goroutine本身可以发出信号表明它已完成工作,因此主要的goroutine可以继续或退出。
这个变体等同于你的变体,更简单,并且不需要done
调用来等待其他goroutine(无论如何这都是错误的和不确定的)。试试Go Playground:
time.Sleep()
另请注意,工作人员goroutine应使用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
}
发出完成信号,因此如果工作人员以某种意外方式返回或发生恐慌,主goroutine将不会等待工作人员。所以它应该像这样开始:
defer
您还可以使用sync.WaitGroup
将主goroutine同步到工作人员(等待它)。实际上,如果您打算使用多个工作器goroutine,那么比从defer func() {
done <- struct{}{} // Signal that we processed all jobs
}()
通道读取多个值更清晰。另外用done
表示完成更简单,因为它有一个Done()
方法(这是一个函数调用)所以你不需要匿名函数:
WaitGroup
有关defer wg.Done()
的完整示例,请参阅JimB's anwser。
如果您想使用多个工作器goroutine,使用WaitGroup
也是惯用的:通道是同步的,因此您不需要任何额外的代码来同步对for range
频道的访问或收到的作业从中。如果您关闭todo
中的todo
频道,则会正确发出所有工作人员的信号。但当然所有排队的工作都将被接收和处理一次。
现在采用使用main()
的变体来使主要的goroutine等待工作者(JimB的回答):如果你想要超过1个工人goroutine怎么办;同时处理你的工作(很可能并行)?
您需要添加/更改代码的唯一方法是:真正启动多个代码:
WaitGroup
不改变任何其他内容,您现在拥有一个正确的并发应用程序,它使用10个并发的goroutine接收和处理您的作业。而且我们没有使用任何“丑陋的”for i := 0; i < 10; i++ {
wg.Add(1)
go ProcessToDo(todo)
}
(我们使用了一个但仅用于模拟慢速处理,而不是等待其他goroutines),并且您不需要任何额外的同步。
答案 1 :(得分:4)
让频道的消费者关闭它通常是一个坏主意,因为在封闭频道上发送是一种恐慌。
在这种情况下,如果您不希望在发送所有消息之前中断消费者,只需使用for...range
循环并在完成后关闭频道。您还需要一个像WaitGroup
这样的信号来等待goroutine完成(而不是使用time.Sleep)
http://play.golang.org/p/r97vRPsxEb
var wg sync.WaitGroup
func ProcessToDo(todo chan string) {
defer wg.Done()
for work := range todo {
fmt.Printf("todo: %q\n", work)
time.Sleep(100 * time.Millisecond)
}
fmt.Printf("Shutting down ProcessToDo - todo channel closed!\n")
}
func main() {
todo := make(chan string, 100)
wg.Add(1)
go ProcessToDo(todo)
for i := 0; i < 20; i++ {
todo <- fmt.Sprintf("Message %02d", i)
}
fmt.Println("*** all messages queued ***")
close(todo)
wg.Wait()
}
答案 2 :(得分:0)
我认为接受的答案对于此特定示例非常有效。但是,要回答“在缓冲区为空后关闭“工作者”例程”的问题-可能会有更优雅的解决方案。
工作者可以在缓冲区为空时返回,而无需通过关闭通道发出信号。
如果工作人员需要处理的任务数量未知,这特别有用。
在此处查看:https://play.golang.org/p/LZ1y0eIRMeS
package main
import (
"fmt"
"time"
"math/rand"
)
func main() {
rand.Seed(time.Now().UnixNano())
ch := make(chan interface{}, 10)
go worker(ch)
for i := 1; i <= rand.Intn(9) + 1; i++ {
ch <- i
}
blocker := make(chan interface{})
<-blocker
}
func worker(ch chan interface{}){
for {
select {
case msg := <- ch:
fmt.Println("msg: ", msg)
default:
fmt.Println("exiting worker")
return
}
}
}