Golang中无缓冲通道的默认select等待时间

Golang中无缓冲通道的默认select等待时间 我确信我在这里做了些蠢事——但我就是看不出来:

看起来,从一个无缓冲通道进行的默认 select 操作有一个我无法避免的超时。

我有以下 init() 函数:

func init() {
	moves = make(moveChannel, 0)
	go updateRelativeMove(moves)
}

然后我有这个更新函数:

func updateRelativeMove(moves moveChannel) {
	move_pair := newMovePair()
	for {
		select {
		case new_move := <-moves:
			if new_move.x != nil {
				move_pair.x = new_move.x
			}
			if new_move.y != nil {
				move_pair.y = new_move.y
			}
			fmt.Println("<-", *move_pair.x, *move_pair.y)
		default:
			interval(move_pair)
			time.Sleep(INTERVAL)
		}
	}
}

其中 INTERVALconst INTERVAL = time.Duration(time.Second / 30),并且我也检查了运行时的值——它报告为 '33.333333ms',即 0.033 秒或 1/30 秒。

实际发生的情况是,我的 interval 函数每秒被调用约 15 次,因此更新(当然)计算错误——但我期望这大约是每秒 30 次。请注意,这是在第一个 case 不匹配时发生的——即没有从通道接收到消息。

我可以在运行时计算自上次 interval 调用以来的时间——但这应该是不必要的——interval 应该每秒被调用 30 次——我唯一能想到的是,无缓冲消息读取本身占用了剩余的时间——即使这每秒也只有大约 15 次调用?!

注意:如果我将间隔改为 1/60 秒,我得到约 38 次调用/秒

请提供任何帮助——致以最美好的祝愿——Andy


更多关于Golang中无缓冲通道的默认select等待时间的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

进一步来说,似乎 RobotGo 引入了高达 327.1859 毫秒的延迟——推测是 C 语言接管了 Windows 线程……

更多关于Golang中无缓冲通道的默认select等待时间的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我解决了这个问题——存在一个较小的操作系统延迟(例如25%的延迟,因此值得计算耗时)以及一个由我的代码引起的较大延迟。😑

在多线程系统中,有无数因素可能影响执行时序。其他线程、驱动程序、定时中断、垃圾回收器、内核活动、杀毒软件扫描、页面错误、磁盘访问……所有这些都可能在实际系统中显著改变你的计时结果。

所有现代游戏引擎都使用计时器来计算自上次更新以来实际经过的时间,而不是依赖固定值。

好的,我明白了。我将把提供的HTML内容转换为Markdown格式,并严格遵守您提出的所有规则:准确翻译英文文本、保持代码原样并用指定语言代码块包裹、移除用户和日期信息,并只输出最终的Markdown文档。


好的 - 我修复了它 - 以下是解决方案,希望对其他人有所帮助。

我将默认值改为:

		case <-time.After(INTERVAL):
			interval(move_pair)
		}

并且用秒表更准确地重新测量了时间 - 现在可以正常工作了…

在无缓冲通道的select语句中,default分支确实会立即执行,没有内置的超时机制。你遇到的问题很可能是由于interval()函数的执行时间导致的,而不是select本身的问题。

让我分析一下你的代码:

func updateRelativeMove(moves moveChannel) {
    move_pair := newMovePair()
    for {
        select {
        case new_move := <-moves:
            // 处理接收到的移动数据
            if new_move.x != nil {
                move_pair.x = new_move.x
            }
            if new_move.y != nil {
                move_pair.y = new_move.y
            }
            fmt.Println("<-", *move_pair.x, *move_pair.y)
        default:
            interval(move_pair)
            time.Sleep(INTERVAL)
        }
    }
}

问题在于:当没有数据可接收时,代码会执行default分支,先调用interval()函数,然后睡眠INTERVAL时间。如果interval()函数本身需要执行时间(比如5ms),那么每次循环的实际间隔就是INTERVAL + interval()执行时间。

要验证这一点,可以在interval()函数前后添加时间测量:

default:
    start := time.Now()
    interval(move_pair)
    elapsed := time.Since(start)
    fmt.Printf("interval执行时间: %v\n", elapsed)
    time.Sleep(INTERVAL)

如果你需要更精确的定时,应该使用time.Ticker而不是在default分支中sleep:

func updateRelativeMove(moves moveChannel) {
    move_pair := newMovePair()
    ticker := time.NewTicker(INTERVAL)
    defer ticker.Stop()
    
    for {
        select {
        case new_move := <-moves:
            if new_move.x != nil {
                move_pair.x = new_move.x
            }
            if new_move.y != nil {
                move_pair.y = new_move.y
            }
            fmt.Println("<-", *move_pair.x, *move_pair.y)
        case <-ticker.C:
            interval(move_pair)
        }
    }
}

这样interval()函数会以更精确的INTERVAL间隔被调用,不受函数执行时间的影响。ticker会确保每次调用的间隔尽可能接近INTERVAL,即使interval()的执行时间有波动。

如果你确实需要在没有消息时以固定频率执行interval(),同时又要能即时响应通道消息,上面的ticker方案是最合适的。

回到顶部