rpc服务端默认并发执行,每个请求由独立goroutine处理,无需手动加go;runtime.gomaxprocs不影响rpc并发数,仅控制p数量;有效并发控制须在业务层实现,如用带缓冲channel作信号量。

RPC服务端默认就并发执行,不需要手动加 go
Go标准库的 net/rpc(包括 rpc.HandleHTTP())在接收到请求后,会由 http.Serve 自动为每个连接或每个 RPC 调用启动一个新 goroutine 来处理。你写的 Add、Multiply 这类方法,天然就在独立 goroutine 中运行——根本不用自己写 go t.Add(...)。
这意味着:不加任何控制时,并发度完全取决于客户端请求数量,可能瞬间起成百上千个 goroutine,尤其在批量调用或压测场景下极易失控。
别碰 runtime.GOMAXPROCS,它不管 RPC 并发数
runtime.GOMAXPROCS 控制的是 P(Processor)数量,即 OS 线程可并行执行的逻辑处理器上限,不是 goroutine 并发上限。设成 1 不会让 1000 个 RPC 请求串行执行,它们仍会在单个 P 上快速切换调度,只是无法利用多核并行——但调度队列压力反而更大,延迟可能更高。
- 它影响的是调度粒度和系统线程映射,不是业务并发控制点
- 默认值已是
runtime.NumCPU(),通常无需修改 - 盲目调小可能加剧
runtime.findrunnable调度开销
真正有效的并发控制必须落在业务层
RPC 方法本身是同步函数调用,控制点只能放在方法内部或其调用链上。常见且可靠的做法有两类:
- 用带缓冲 channel 做信号量:在方法入口处
sem ,退出前 <code>,<code>sem := make(chan struct{}, N)即最大 N 个并发 - 把耗时操作(如 DB 查询、HTTP 调用)移到协程池中执行,RPC 方法只负责入队,避免阻塞 handler goroutine
- 对共享状态加锁(如
sync.Mutex)不是控制并发度,而是保证安全;它反而可能放大排队等待,不能替代限流
示例信号量用法:
var sem = make(chan struct{}, 5) // 最多 5 个并发
<p>func (t <em>Arith) Multiply(args </em>Args, reply *int) error {
sem </p><pre class="brush:php;toolbar:false;">// 实际工作:这里才真正执行耗时逻辑
*reply = args.A * args.B
time.Sleep(100 * time.Millisecond)
return nil}
注意 HTTP handler 的隐式 goroutine 生命周期
使用 rpc.HandleHTTP() 时,底层走的是 http.DefaultServeMux,每个请求由 http.Server 新启 goroutine 处理。这个 goroutine 会完整跑完整个 RPC 反射调用链——也就是说,如果你在 Multiply 里又起了 goroutine 去做异步任务,但没等它结束就返回,客户端会立刻收到响应,而后台任务仍在运行,容易造成 goroutine 泄漏。
关键点:
- RPC 方法返回即代表响应已发出,goroutine 生命周期在此结束
- 想做异步处理,必须显式用
sync.WaitGroup或context管理子 goroutine - 信号量要放在方法最外层,否则可能被绕过(比如错误提前 return 没释放)
最易被忽略的是:信号量释放必须用 defer 包裹,且不能放在条件分支里——否则 panic 或 early return 会导致 channel 堵死,后续所有请求永久卡住。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











