Effective Go 中的客户端请求处理程序示例导致死锁?

Client request handler example from Effective Go leads to deadlock?

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 通道只针对一个请求,所以一旦一个请求进来,所有的 goroutines 都可以访问同一个请求。为什么这有用?

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

在您的代码中,serve 启动处理程序 goroutine,然后等待 <-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)
}

关于你的最后一个问题:多个处理程序不会看到相同的请求。一旦一个处理程序从通道接收到一个值,该值就会从中删除。下一个处理程序将接收下一个值。将通道视为可以安全并发使用的 First-In-First-Out 队列。

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