Python/Perl: timed loop implementation (also with

2020-06-29 05:38发布

问题:

I would like to use Perl and/or Python to implement the following JavaScript pseudocode:

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
}

 

Now, this is a particularly unlucky example to choose as a basis - but I simply couldn't think of any other language to provide it in :) Basically, there is a 'main loop' and an auxiliary 'loop' (timedCount), which both count at different rates: main with 500 ms period (implemented through a wait), timedCount with 100 ms period (implemented via setInterval). However, JavaScript is essentially single-threaded, not multi-threaded - and so, there is no real sleep/wait/pause or similar (see JavaScript Sleep Function - ozzu.com), which is why the above is, well, pseudocode ;)

By moving the main part to yet another setInterval function, however, we can get a version of the code which can be pasted and ran in a browser shell like JavaScript Shell 1.4 (but not in a terminal shell like 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()");

... which results with something like this output:

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

... that is, the main counts and auxiliary counts are 'interleaved'/'threaded'/'interspersed', with a main count on approx every five auxiliary counts, as anticipated.

 

And now the main question - what is the recommended way of doing this in Perl and Python, respectively?

  • Additionally, do either Python or Perl offer facilities to implement the above with microsecond timing resolution in cross-platform manner?

 

Many thanks for any answers,
Cheers!

回答1:

The simplest and most general way I can think of doing this in Python is to use Twisted (an event-based networking engine) to do this.

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 has a highly efficient and stable event-loop implementation called the reactor. This makes it single-threaded and essentially a close analogue to Javascript in your example above. The reason I'd use it to do something like your periodic tasks above is that it gives tools to make it easy to add as many complicated periods as you like.

It also offers more tools for scheduling task calls you might find interesting.



回答2:

A simple python implementation using the standard library's 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()'

Alternatively, you can't go wrong using an asynchronous library like twisted (demonstrated in this answer) or gevent (there are quite a few more out there).



回答3:

For Perl, for default capabilities, in How do I sleep for a millisecond in Perl?, it is stated that:

  • sleep has resolution of a second
  • select accepts floating point, the decimal part interpreted as milliseconds

And then for greater resolution, one can use Time::HiRes module, and for instance, usleep().

If using the default Perl capabilities, the only way to achieve this 'threaded' counting seems to be to 'fork' the script, and let each 'fork' act as a 'thread' and do its own count; I saw this approach on Perl- How to call an event after a time delay - Perl - and the below is a modified version, made to reflect the OP:

#!/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();
}


回答4:

Here's another Perl example - without fork, with Time::HiRes with usleep (for main) and setitimer (for auxiliary) - however, it seems that the setitimer needs to be retriggered - and even then, it seems just to run through the commands (not actually wait):

#!/usr/bin/env perl

use strict;
use warnings;

use Time::HiRes qw(usleep ITIMER_VIRTUAL setitimer);

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
    usleep(500000);
  }
};

my $tstart = 0;
sub timedCount()
{
  #~ print "timedCount\n";
  if ($c < 10) {
    $c = $c + 1;
    print("c=" . $c . "\n");

    # if we want to loop with VTALRM - must have these *continuously*
    if ($tstart == 0) {
      #~ $tstart = 1; # kills the looping
      $SIG{VTALRM} =  &timedCount;
      setitimer(ITIMER_VIRTUAL, 0.1, 0.1);
    }
  }
};


# main:

$SIG{VTALRM} =  &timedCount;
setitimer(ITIMER_VIRTUAL, 0.1, 0.1);

mainCount();

 

EDIT: Here is even a simpler example with setitimer, which I cannot get to time out correctly (reardless of ITIMER_VIRTUAL or ITIMER_REAL), it simply runs as fast as possible:

use strict;
use warnings;

use Time::HiRes qw ( setitimer ITIMER_VIRTUAL ITIMER_REAL time );

sub ax() {
  print time, "\n";
  # re-initialize
  $SIG{VTALRM} = &ax; 
  #~ $SIG{ALRM} = &ax; 
}

$SIG{VTALRM} = &ax;
setitimer(ITIMER_VIRTUAL, 1e6, 1e6); 

#~ $SIG{ALRM} = &ax;
#~ setitimer(ITIMER_REAL, 1e6, 1e6);