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

del 阅读:60 2025-06-02 22:19:02 评论:0

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

请您参考如下方法:

调用serve的代码不应与填充 channel 的代码在同一goroutine中运行。
在您的代码中,serve启动处理程序goroutine,但随后等待<-quit。由于已被阻止,因此您永远不会到达填充serveChannel的代码。因此, worker 永远不会有任何消耗。您也永远不会通知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) 
} 
至于您的最后一个问题:多个处理程序将看不到相同的请求。当一个处理程序从 channel 接收到一个值时,该值将从其中删除。下一个处理程序将接收下一个值。将 channel 视为可以并发使用的先进先出队列。
您可以在 the playground上找到代码的最后迭代。


标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

关注我们

一个IT知识分享的公众号