我们通常使用的系统,不管是linux还是windows,都是非实时系统。非实时系统可以获得很精确的当前时间,甚至可以通过读取cpu的某些寄存器得到以cpu周期计数的时钟,估计除了GPS系统之外足以应付我们日常能够碰到的应用,但是,非实时系统并没有提供精确的定时器实现。如果在linux或者windows系统中使用系统定时器,尽管我们可以创建精度为1ms甚至更小的定时器,但是实际执行的时候可以发现,定时器总是需要几ms甚至十几ms才会被调度一次,而不能达到期望的精度。
通常情况下10ms量级的定时器可以满足应用的需求,但一些特殊场景下10ms就显得太长了,比如我碰到过一个场景,在一个单向网络上将数据推动到一台设备上,推送速率大约100Mbps量级。如果每10ms推送一次,那么每次推送的数据量大约是1Mb,可是那个接收设备比较老,没有这么大的接收buffer,很容易造成丢包,而单向网络上是没有办法进行数据重传的,如果我们降低一次推送的数据量,那么总体的传输效率就会相应下降。因此,唯一可行的方法是我们根据接收设备的IO大小,尽可能“平滑”的进行数据的推送,我们需要更精细的时钟对推送行为进行控制。
对于linux系统,理论上我们可以重新编译内核,减少系统时钟的调用间隔,但这样做动静太大,可能影响许多系统模块的运行,存在风险,我们还是期望能够在应用层解决问题。经过尝试,最终我们找到了问题的解决方案:
感谢cpu厂商为我们提供的多核系统,我们牺牲一个内核,在这个核上跑一个忙等待的检查线程,它的作用就是用死循环的方式不断的读取系统时间,并通知推送线程(可以是多个)是否可以启动推送。推送线程平时等待在一个信号量上,当时间到期之后,检查线程置位信号量,推送线程被激活并进行推送。按照这个方式,我们可以实现几乎任意精度的定时器。
这个方案的缺点是有一个cpu内核被浪费了,好在服务器一般都有4、8甚至16个内核,浪费一个核的代价还是可以接受的。
注:我们为该方案申请了专利并已经获得授权,专利名称:一种在非实时系统上精确网络限速的方法,授权号CN102368729B。