未解锁的sync.Mutex为何会阻止http.ResponseWriter发送HTTP响应?
我知道应该用defer来解锁sync.Mutex,避免报错时未解锁导致其他goroutine阻塞,但搞不懂——如果注释掉h.dbMutex.Unlock(),为什么第一个拿到锁的goroutine连HTTP响应都返回不了?
代码示例:
type HandlerWrapper struct { db *sql.DB dbMutex *sync.Mutex }
handlerWrapper := HandlerWrapper{db : db, dbMutex: &sync.Mutex{}} // 使用go chi路由 router.Get("/items", handlerWrapper.GetAllHandler) http.ListenAndServe(":5000", router)
func (h *HandlerWrapper) GetAllHandler(w http.ResponseWriter, r *http.Request){ h.dbMutex.Lock() //defer h.dbMutex.Unlock() // 注释掉这行后,无HTTP响应,为什么? rows, err := h.db.Query("SELECT * FROM lst") // 这行故意返回错误 if err != nil { log.Printf(": %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } }
首先明确:首个请求的goroutine其实已经生成并发送了响应,你觉得没收到大概率是测试场景的干扰,或者对goroutine和锁的关系理解有偏差,具体原因拆解如下:
响应生成逻辑是正常执行的
当db.Query报错后,代码会执行log.Printf打印错误,接着调用http.Error把错误信息和500状态码写入ResponseWriter,最后return结束goroutine。Go的HTTP服务器会在处理请求的goroutine退出时,自动把缓冲区里的响应内容发送给客户端,理论上客户端应该能收到响应。你误以为“无响应”的核心原因
最常见的场景是:测试时同时发起了多个请求(比如浏览器开多个标签页、脚本并发请求)。首个请求的goroutine持有锁退出后,锁始终处于锁定状态,后续所有请求的goroutine都会在h.dbMutex.Lock()处阻塞。而客户端(比如Chrome)对同一个域名的并发连接数有限制(通常是6个),当后续请求占满连接数,首个请求的响应会被客户端延迟显示,让你误以为首个请求也没响应。锁未释放不会影响首个请求的响应发送
sync.Mutex是你自定义的锁,和HTTP服务器的内部响应发送逻辑完全无关。持有锁的goroutine退出后,锁的状态会停留在“锁定”,但这只会阻塞后续请求,不会影响已经生成的响应发送。验证方式
用curl单独发起一个请求,不要同时发起多个:curl http://localhost:5000/items这时候你应该能立刻收到500错误的响应,同时服务器日志也会打印对应的错误信息,以此证明首个请求的响应是正常发送的。
内容的提问来源于stack exchange,提问作者jmah27

