【问题标题】:Client request handler example from Effective Go leads to deadlock?Effective Go 中的客户端请求处理程序示例导致死锁?
【发布时间】:2020-12-10 21:16:53
【问题描述】:

Effective Go 指南有以下处理客户端请求的示例:

func handle(queue chan *Request) {
    for r := range queue {
        process(r)
    }
}

func Serve(clientRequests chan *Request, quit chan bool) {
    // Start handlers
    for i := 0; i < MaxOutstanding; i++ {
        go handle(clientRequests)
    }
    <-quit  // Wait to be told to exit.
}

我在本地运行了类似的代码,其中客户端请求只是整数:

func handle(queue chan int) {
    for r := range queue {
        fmt.Println("r = ", r)
    }
}

func serve(clientRequests chan int, quit chan bool) {
    // Start handlers
    for i := 0; i < 10; i++ {
        go handle(clientRequests)
    }
    <-quit // Wait to be told to exit.
}

var serveChannel = make(chan int)
var quit = make(chan bool)
serve(serveChannel, quit)
for i := 0; i < 10; i++ {
    serveChannel <- i
}

但是我的代码导致死锁错误fatal error: all goroutines are asleep - deadlock!

即使我从概念上不了解程序中的问题,我也不了解原始代码的工作原理。我确实知道MaxOutstanding goroutines 已生成,它们都收听单个clientRequests 频道。但是clientRequests 通道只针对一个请求,所以一旦有请求进来,所有的 goroutine 都可以访问同一个请求。为什么这有用?

【问题讨论】:

    标签: go concurrency deadlock goroutine


    【解决方案1】:

    调用serve 的代码不应与填满通道的代码在同一个goroutine 中运行。

    在您的代码中,serve 启动处理程序 goroutine,但随后等待 &lt;-quit。由于它被阻止,您永远无法访问填充serveChannel 的代码。所以工人们从来没有任何东西可以消费。你也永远不会通知quit,让serve 永远等待。

    第一步是在单独的 goroutine 中将数据发送到serveChannel。例如:

    func handle(queue chan int) {
        for r := range queue {
            fmt.Println("r = ", r)
        }
    }
    
    func serve(clientRequests chan int, quit chan bool) {
        // Start handlers
        for i := 0; i < 10; i++ {
            go handle(clientRequests)
        }
        <-quit // Wait to be told to exit.
    }
    
    func populateRequests(serveChannel chan int) {
        for i := 0; i < 10; i++ {
            serveChannel <- i
        }
    }
    
    func main() {
        var serveChannel = make(chan int)
        var quit = make(chan bool)
        go populateRequests(serveChannel)
        serve(serveChannel, quit)
    }
    

    我们现在可以按需要处理所有请求。

    但是,一旦处理完成,您仍然会遇到all goroutines are asleep。这是因为serve 最终等待quit 信号,但没有任何东西可以发送。

    在普通程序中,quit 将在捕获信号或某些 shutdown 请求后填充。由于我们什么都没有,所以我们将在三秒后将其关闭,也在一个单独的 goroutine 中。

    func handle(queue chan int) {
        for r := range queue {
            fmt.Println("r = ", r)
        }
    }
    
    func serve(clientRequests chan int, quit chan bool) {
        // Start handlers
        for i := 0; i < 10; i++ {
            go handle(clientRequests)
        }
        <-quit // Wait to be told to exit.
    }
    
    func populateRequests(serveChannel chan int) {
        for i := 0; i < 10; i++ {
            serveChannel <- i
        }
    }
    
    func quitAfter(quit chan bool, duration time.Duration) {
        time.Sleep(duration)
        quit <- true
    }
    
    func main() {
        var serveChannel = make(chan int)
        var quit = make(chan bool)
        go populateRequests(serveChannel)
        go quitAfter(quit, 3*time.Second)
        serve(serveChannel, quit)
    }
    

    至于您的最后一个问题:多个处理程序不会看到相同的请求。一旦一个处理程序从通道接收到一个值,该值就会从通道中删除。下一个处理程序将接收下一个值。将通道想象成一个先进先出队列,可以安全地并发使用。

    您可以在the playground 上找到代码的最后一次迭代。

    【讨论】:

      猜你喜欢
      • 2021-08-26
      • 1970-01-01
      • 2012-07-23
      • 2016-07-15
      • 2011-12-12
      • 2020-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多