缓冲区为空后关闭“worker”进入例行程序

时间:2015-09-03 18:34:36

标签: go concurrency shutdown channel goroutine

我希望我的常规工作程序(下面的代码中为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)
}

3 个答案:

答案 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
        }
    }       
}