
Go 语言中并发处理 HTTP 请求时,如果使用不当,可能会导致请求无响应。这是因为 net/http 包的 ListenAndServe 函数已经自动启动了 goroutine 来处理每个连接。重复启动 goroutine 反而会导致问题。
ListenAndServe 函数内部已经实现了并发处理机制。以下是相关源码的简化版本,重点关注 goroutine 的创建:
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
// ... handle error ...
continue
}
c := srv.newConn(conn)
go c.serve() // 关键:每个连接都在一个 goroutine 中处理
}
}
func (c *conn) serve() {
// ... connection setup ...
handler.ServeHTTP(w, c.req) // 调用你的 Handler
// ... connection cleanup ...
}可以看到,Serve 方法在一个无限循环中接受新的连接,并且为每个连接启动一个新的 goroutine 来处理。这个 goroutine 会调用你的 handler 函数来处理 HTTP 请求。
因此,在你的 handle 函数中,不应该再手动启动 goroutine。 这样做会引入不必要的并发,并且可能导致 ResponseWriter 在多个 goroutine 中被同时访问,从而引发竞争条件和未定义的行为,最终导致请求无响应。
正确的处理方式:
直接在 handle 函数中进行计算和响应,无需额外启动 goroutine。
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
http.HandleFunc("/", handle)
http.ListenAndServe(":8080", nil)
}
func handle(w http.ResponseWriter, r *http.Request) {
// 模拟耗时计算
time.Sleep(1 * time.Second)
// 呈现结果
fmt.Fprint(w, "hello")
}代码解释:
- http.HandleFunc("/", handle): 将根路径 / 注册到 handle 函数。
- http.ListenAndServe(":8080", nil): 在 8080 端口启动 HTTP 服务器。ListenAndServe 函数会监听连接,并为每个连接启动一个 goroutine。
- handle(w http.ResponseWriter, r *http.Request): 处理 HTTP 请求的函数。 w 是 ResponseWriter 接口,用于写入响应; r 是 Request 结构体,包含请求的信息。
- time.Sleep(1 * time.Second): 模拟耗时计算,实际场景中替换为你的图像处理或其他计算逻辑。
- fmt.Fprint(w, "hello"): 将 "hello" 写入响应。
注意事项:
- 避免在 handler 函数中手动启动 goroutine 来处理请求。ListenAndServe 已经为你做了。
- 如果需要在 handler 函数中进行并发操作(例如,并行处理多个数据块),请确保使用适当的同步机制(例如,互斥锁、通道)来保护共享资源,避免竞争条件。
- 如果需要对请求进行更细粒度的控制,可以考虑使用 http.Server 结构体,并自定义 Serve 方法。
总结:
Go 语言的 net/http 包已经提供了内置的并发处理机制。理解 ListenAndServe 函数的工作原理,避免重复启动 goroutine,是解决并发 HTTP 请求无响应问题的关键。 遵循最佳实践,可以编写出高效、稳定的并发 HTTP 服务器。










