在程序中延迟或者等待一段时间一般可以使用Sleep函数实现,但是因为操作系统线程调度的消耗,往往只能做到十几或者数十毫秒的精度,很难达到微秒级,Golang的time.Sleep也是如此。
Sleep函数一般都会将当前线程从CPU让出,然后等待操作系统的重新调度,这样可以有效利用CPU资源,但也是Sleep时间精度不高的“罪魁祸首”。因为操作系统线程调度并不是实时的,它会先对线程排队,然后在合适的时机才进行真正的CPU切换,我们可以想象还会有队列优先级以及插队的情况存在。
那么如何才能降低到微秒级呢?本着有现成的就不造轮子的原则,我找遍了各个技术角落也没有找到,当然可能还是有遗漏的地方。不过虽然没有Golang的实现,但是找到了一些C语言版本的延迟函数。参考它们,我实现了Golang的版本,代码如下:
func DelayMicroseconds(us int64) {
var tv syscall.Timeval
_ = syscall.Gettimeofday(&tv)
stratTick := int64(tv.Sec)*int64(1000000) + int64(tv.Usec) + us
endTick := int64(0)
for endTick < stratTick {
_ = syscall.Gettimeofday(&tv)
endTick = int64(tv.Sec)*int64(1000000) + int64(tv.Usec)
}
}
你可能会说,这不就是一个死循环吗?!是的,这就是一个死循环,也因此它的名字是Delay,而不是Sleep。
这里边通过系统调用获取了精确到微秒级别的当前时间,然后通过循环不断获取当前时间,与deadline进行比较,在达到或超出deadline时跳出循环,最终实现了指定微秒的时间延迟。
这是目前找到的最好的实现方式了,虽然会导致CPU繁忙,但是也只是在短暂的微秒级别,有时候还是能接受的。
天空飘来五个字,那都不是事,是事也就烦一会,一会就没事。
经过实测这个函数在Linux上可以实现微秒级别的延迟,不过虽然Windows上也有Gettimeofday的系统调用,但是最低只能做到大概500us左右的延迟,通过看源码内部实现机制差别很大。
这段代码用在我编写的一个树莓派硬件驱动库中,如有兴趣欢迎拍砖:
如果你有更好的方法,欢迎指正。