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

标签 go concurrency deadlock goroutine

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上找到代码的最后迭代。

关于go - Effective Go中的客户请求处理程序示例导致死锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63523648/

相关文章:

mysql - 查询正在锁定表,无法终止该进程

json - 将 JSON 参数传递给 GOLANG 中的函数

java - 线程无法捕获 InterruptionException

c++ - asio::async_write 在大容量流上同步非常困难

java - 同时向一个 URL 发送多个 POST 请求

Golang 可见性或 CPU 线程缓存问题

concurrency - 将 Hikari 事务器用于 Doobie 和 ZIO 时遇到死锁

google-app-engine - 谷歌应用引擎 api_server 与模块

search - 包含 slice 的结构集

go - GOARCH 和 GOOS 在 go toolchain 编译过程中是如何使用的?