Goroutine为何偶尔出现高延迟?底层实现原因解析
问题描述
当创建100或1000个goroutine时,观测到偶尔会出现超过1ms的延迟。测试代码如下:
func main() { for i := 0; i < 100; i++ { // or use 1000 go func() { testWaitRoutine() }() } time.Sleep(10 * time.Second) } func testWaitRoutine() { channl := make(chan int, 1) go func() { channl <- time.Now().Nanosecond() time.Sleep(3 * time.Second) channl <- time.Now().Nanosecond() }() _, secondTime := <-channl, <-channl nowTime := time.Now().Nanosecond() delay := nowTime - secondTime println(delay) }
输出结果中存在少量微秒级和毫秒级的延迟数据,例如:
... 3000 338000 2000 ... 1000 1000 72000 1000 ... 2000 2000 1185000 1000 1000 ...
硬件环境:2017款MBP和Windows 10系统均出现该现象。
疑问:为何goroutine有时会出现远高于稳定值的延迟?是什么底层实现导致的?
分析与解答
- Goroutine调度的抢占机制限制:Go采用M:N调度模型,即便1.14版本引入了异步抢占,调度也无法做到绝对实时。当goroutine执行长时间系统调用(比如
time.Sleep)或阻塞操作时,调度器会将M(操作系统线程)从P(逻辑处理器)解绑,但如果系统中P资源紧张、有大量goroutine等待调度,被唤醒的goroutine可能无法立即获得时间片,进而产生延迟。 - 操作系统线程调度的干扰:Goroutine最终运行在操作系统线程上,而系统本身的线程调度存在不确定性。当系统中有其他进程、线程占用CPU资源时,操作系统可能临时挂起Go的M线程,切换到其他任务,这会直接导致goroutine执行被延迟,表现为观测到的毫秒级波动。
- 系统定时器的精度限制:
time.Sleep的精度依赖操作系统定时器,Windows默认定时器精度约1ms,macOS也有类似粒度。定时器到期时,操作系统唤醒对应线程的过程本身就可能存在延迟,尤其是系统负载较高时,这种延迟会被放大。 - 通道操作的潜在竞争开销:虽然通道操作轻量,但大量goroutine同时进行通道收发时,可能触发调度器额外调度动作,或出现短暂的资源竞争,这也会引发少量延迟波动。
内容的提问来源于stack exchange,提问作者LoranceChen
相关产品推荐
相关产品推荐

