You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

两个Goroutine向同一Channel发送消息耗时差异显著的原因探究

Go带缓冲Channel发送耗时差异的原因分析

实验场景

使用Go 1.18开展如下实验:

  • 创建缓冲大小为1000的chan *Message
  • 启动两个Goroutine:
    • 第一个Goroutine:每次休眠1ms后发送type1消息,循环执行100次
    • 第二个Goroutine:每次休眠100ms后发送type2消息,循环执行100次
  • 统计两者的消息发送耗时,结果显示type2的总发送耗时远高于type1,但预期两者耗时应相近。

实验代码如下:

package main

import (
    "log"
    "time"
)

type Message struct {
    msgID     int
    timeStamp int64
    timeTime  time.Time
    msgType   int
}

func main() {

    const buffer = 1000

    msgChan := make(chan *Message, buffer)
    timeSumType1 := time.Time{}
    timeSumType2 := time.Time{}

    go func() {

        for i := 1; i <= 100; i++ {
            time.Sleep(time.Millisecond)
            timeT1 := time.Now()
            msgChan <- &Message{msgID: i, msgType: 1}
            timeDiff := time.Now().Sub(timeT1)
            log.Println("type 1, id :", i, " <- timeDelta:", timeDiff)
            timeSumType1 = timeSumType1.Add(timeDiff)

        }

    }()

    go func() {

        for i := 1; i <= 100; i++ {
            time.Sleep(100 * time.Millisecond)
            timeT1 := time.Now()
            msgChan <- &Message{msgID: i, msgType: 2}
            timeDiff := time.Now().Sub(timeT1)
            log.Println("type 2, id :", i, " <- timeDelta:", timeDiff)
            timeSumType2 = timeSumType2.Add(timeDiff)
        }

    }()

    time.Sleep(time.Second * 20)

    log.Println("type 1, timeSum:", timeSumType1)
    log.Println("type 2, timeSum:", timeSumType2)
}

差异原因及Go内部影响因素

1. Goroutine调度的唤醒延迟差异

  • type2的Goroutine每次休眠100ms,属于长周期休眠,会被放入Go调度器的定时器堆中。当休眠到期后,调度器需要将其从堆中取出、重新绑定到M(操作系统线程),这个过程的延迟远高于短休眠(1ms)的type1 Goroutine。
  • 短休眠的Goroutine会被更频繁地唤醒,调度器对这类任务的调度响应更快,而长休眠Goroutine被唤醒后,可能需要等待当前M上的其他Goroutine执行完毕,或者经历M/P的重新绑定流程,导致发送操作的前置延迟增加。

2. Channel发送的隐性锁竞争

  • 即使Channel缓冲足够大不会阻塞,发送操作内部依然需要获取互斥锁来保护缓冲队列的状态。
  • type1的Goroutine发送频率极高(每1ms一次),会频繁抢占和释放这个锁。type2的Goroutine每次发送时,大概率会遇到type1正在持有锁,需要等待锁释放,这会直接拉长单次发送的耗时。

3. 耗时统计的放大效应

  • type1的单次发送耗时本身极短(纳秒级),即使累加100次,总耗时依然有限。
  • type2的单次发送因调度延迟和锁等待,耗时会比type1高一个数量级,累加100次后,总耗时的差异会被显著放大。

4. 调度器优先级的隐性倾斜

  • Go调度器会优先调度短周期、频繁执行的Goroutine,这类任务被标记为“活跃”任务,获得更多的CPU时间片。而长休眠的Goroutine属于“非活跃”任务,唤醒后需要排队等待调度,进一步增加了发送操作的耗时。

内容的提问来源于stack exchange,提问作者cheng xu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 13:32:13