
go 的 net/rpc 框架基于 http.server 实现,而每个 rpc 请求均由独立的 goroutine 处理,因此服务端方法(如 multiply)总是在新启动的 goroutine 中运行,与主 goroutine 和其他请求完全隔离。
go 的 net/rpc 框架基于 http.server 实现,而每个 rpc 请求均由独立的 goroutine 处理,因此服务端方法(如 multiply)总是在新启动的 goroutine 中运行,与主 goroutine 和其他请求完全隔离。
在 Go 的 net/rpc 实现中,RPC 并非直接“穿透”到服务端函数,而是通过 HTTP 协议承载 RPC 消息(默认路径为 /rpc),并依赖 http.Server 进行底层连接管理。关键在于:http.Serve() 为每个到来的 HTTP 连接(进而每个 RPC 请求)自动启动一个独立的 Goroutine。
查看官方文档对 http.Serve 的说明:
Serve accepts incoming HTTP connections on the listener l, creating a new service goroutine for each. The service goroutines read requests and then call handler to reply to them.
而 rpc.HandleHTTP() 的作用是向 http.DefaultServeMux 注册两个处理器:
- 一个用于处理 RPC 请求(路径默认为 "/rpc");
- 另一个用于调试界面(路径默认为 "/debug/rpc")。
这意味着你写的 Multiply 方法,本质上是被 http.Handler 包装后,在 http.Server 分配的 Goroutine 中执行的——不是在 main Goroutine 中,也不是在 go http.Serve(l, nil) 启动的那个 Goroutine 中,而是每次请求专属的新 Goroutine。
以下是一个更清晰的结构示意:
func main() {
arith := new(Arith)
rpc.Register(arith)
rpc.HandleHTTP() // ← 注册 /rpc 和 /debug/rpc 到 DefaultServeMux
l, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal(err)
}
// 启动 HTTP 服务:此 goroutine 仅负责监听和分发
go http.Serve(l, nil) // ← 主监听 goroutine(不处理业务)
// 此时:客户端发起 client.Call("Arith.Multiply", args, &reply)
// → 触发 HTTP POST 到 /rpc → http.Serve 启动新 goroutine → 路由到 rpc handler → 反射调用 Multiply
}✅ 正确理解:
- client.Call(...) 是同步阻塞调用(等待响应),但服务端逻辑始终在新 Goroutine 中并发执行;
- 无需手动 go t.Multiply(...) —— http.Server 已为你完成并发调度;
- 因此 Multiply 方法天然支持并发,但需自行保证其内部状态(如字段)的并发安全(例如使用 sync.Mutex)。
⚠️ 注意事项:
- 若服务端方法耗时较长或涉及阻塞 I/O(如数据库查询、外部 API 调用),它不会阻塞其他请求,这是 Goroutine 隔离带来的优势;
- 但若方法内启用了长时间运行且未受控的 goroutine(如 go longTask()),需注意资源泄漏和生命周期管理;
- rpc.Register 仅注册一次;重复注册会 panic,建议在 main 初始化阶段完成。
总之,Go RPC 的并发模型简洁而可靠:HTTP 层负责 Goroutine 分发,RPC 层专注序列化与方法路由,开发者只需关注业务逻辑本身。

















