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

问题描述

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个goroutine,它们都收听单个clientRequests通道。但是clientRequests通道仅用于一个请求,因此一旦一个请求进入,所有goroutine都可以访问同一请求。为什么这样有用?

解决方法

调用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 = ",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上找到代码的最后迭代。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...