Python/Perl: 如何实现带微秒的定时循环?

1 投票
4 回答
1745 浏览
提问于 2025-04-17 01:19

我想用Perl和/或Python来实现以下的JavaScript伪代码:

var c=0;
function timedCount()
{
  c=c+1;
  print("c=" + c);

  if (c<10)  {
    var t;
    t=window.setTimeout("timedCount()",100);
  }
}

// main:
timedCount();
print("after timedCount()");

var i=0;
for (i=0; i<5; i++) {
  print("i=" + i);
  wait(500); //wait 500 ms
}

 

这个例子其实不太好,但我实在想不到其他语言来提供这个例子 :) 基本上,有一个“主循环”和一个辅助的“循环”(timedCount),它们的计数速度不一样:主循环每500毫秒执行一次(通过wait实现),而timedCount每100毫秒执行一次(通过setInterval实现)。不过,JavaScript本质上是单线程的,也就是说,它不能同时做多件事,所以没有真正的sleep/wait/pause这样的功能(可以参考 JavaScript Sleep Function - ozzu.com),这就是为什么上面的代码只是伪代码的原因 ;)

不过,如果把主要部分放到另一个setInterval函数里,我们就能得到一个可以在浏览器的控制台中运行的代码,比如在JavaScript Shell 1.4中(但不能在像EnvJS/Rhino这样的终端中运行):

var c=0;
var i=0;
function timedCount()
{
  c=c+1;
  print("c=" + c);

  if (c<10)  {
    var t;
    t=window.setTimeout("timedCount()",100);
  }
}

function mainCount() // 'main' loop
{
  i=i+1;
  print("i=" + i);

  if (i<5)  {
    var t;
    t=window.setTimeout("mainCount()",500);
  }
}

// main:
mainCount();
timedCount();
print("after timedCount()");

... 这样会得到类似下面的输出:

i=1
c=1
after timedCount()
c=2
c=3
c=4
c=5
c=6
i=2
c=7
c=8
c=9
c=10
i=3
i=4
i=5

... 也就是说,主计数和辅助计数是“交替进行”的,主计数大约每五次辅助计数执行一次,正如预期的那样。

 

现在主要的问题是,分别用Perl和Python来实现这个的推荐方法是什么?

  • 另外,Python或Perl是否提供了可以以微秒级别的时间精度在跨平台上实现上述功能的工具?

 

非常感谢任何回答,
祝好!

4 个回答

2

在Perl中,关于默认功能,在如何在Perl中暂停一毫秒?这个问题里提到:

  • sleep的精度是以秒为单位的
  • select可以接受小数,整数部分是秒,小数部分则被当作毫秒来处理

如果想要更高的精度,可以使用Time::HiRes模块,比如可以使用usleep()这个函数。

如果只用Perl的默认功能,似乎唯一能实现这种“线程”计数的方法就是“分叉”脚本,让每个“分叉”作为一个“线程”来进行自己的计数。我在Perl - 如何在延迟后调用事件上看到过这种方法,下面是一个修改过的版本,反映了提问者的需求:

#!/usr/bin/env perl

use strict;
my $pid;

my $c=0;
my $i=0;

sub mainCount()
{
  print "mainCount\n";
  while ($i < 5) {
    $i = $i + 1;
    print("i=" . $i . "\n");
    select(undef, undef, undef, 0.5); # sleep 500 ms
  }
};

sub timedCount()
{
  print "timedCount\n";
  while ($c < 10) {
    $c = $c + 1;
    print("c=" . $c . "\n");
    select(undef, undef, undef, 0.1); # sleep 100 ms
  }
};


# main:
die "cant fork $!\n" unless defined($pid=fork());

if($pid) {
  mainCount();
} else {
  timedCount();
}
3

这里有一个简单的Python实现,使用的是标准库里的threading.Timer

from threading import Timer

def timed_count(n=0):
    n += 1
    print 'c=%d' % n
    if n < 10:
        Timer(.1, timed_count, args=[n]).start()

def main_count(n=0):
    n += 1
    print 'i=%d' % n
    if n < 5:
        Timer(.5, main_count, args=[n]).start()

main_count()
timed_count()
print 'after timed_count()'

另外,你也可以选择使用一些异步库,比如twisted(在这个回答中有演示)或者gevent(还有很多其他的库可以选择)。

4

我想到的在Python中最简单、最通用的方法就是使用Twisted,这是一种基于事件的网络引擎。

from twisted.internet import reactor
from twisted.internet import task

c, i = 0, 0
def timedCount():
    global c
    c += 1
    print 'c =', c

def mainCount():
    global i
    i += 1
    print 'i =', i

c_loop = task.LoopingCall(timedCount)
i_loop = task.LoopingCall(mainCount)
c_loop.start(0.1)
i_loop.start(0.5)
reactor.run()

Twisted有一个非常高效且稳定的事件循环实现,叫做反应器(reactor)。它是单线程的,基本上和你上面提到的Javascript很相似。我之所以推荐用它来处理你提到的定期任务,是因为它提供了一些工具,可以让你轻松添加任意复杂的时间周期。

它还提供了更多调度任务调用的工具,你可能会觉得这些很有趣。

撰写回答