Python / Perl: реализация временного цикла (также с микросекундами)?

1

Я хотел бы использовать 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 предлагать средства для реализации вышеизложенного с микросекундным временным разрешением в кросс-платформенном режиме?

 

Большое спасибо за любые ответы,
Ура!

Теги:
loops
timing

4 ответа

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 имеет высокоэффективную и стабильную реализацию цикла событий, называемую реактором. Это делает его однопоточным и по сути близким аналогом Javascript в вашем примере выше. Причина, по которой я буду использовать его, чтобы сделать что-то вроде ваших периодических задач выше, заключается в том, что он дает инструменты, позволяющие легко добавлять столько сложных периодов, сколько вам захочется.

Он также предлагает больше инструментов для планирования вызовов задач, которые могут показаться вам интересными.

  • 0
    Круто @rlotun - большое спасибо за это, даже не слышал о twisted , хорошей ссылке тоже ... Ура!
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 (там еще немало).

  • 0
    Большое спасибо @zeekay - приятно иметь стандартный подход; Я никогда не слышал о gevent также ... Ура!
2

Для Perl, для возможностей по умолчанию, в Как спать за миллисекунду в Perl?, указано, что:

  • sleep имеет разрешение второй
  • select принимает плавающую точку, десятичная часть интерпретируется как миллисекунды

И затем для большего разрешения можно использовать модуль Time::HiRes и, например, usleep().

При использовании возможностей Perl по умолчанию единственным способом достижения этого "потокового" подсчета является "разветвление" script, и пусть каждая "вилка" действует как "поток" и выполняет свой собственный счет; Я видел этот подход на Perl- Как вызвать событие после задержки - Perl - и ниже - модифицированная версия, сделанная для отражения 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();
}
  • 1
    Не используйте select для сна - вместо этого следует использовать Time :: HiRes.
  • 0
    Большое спасибо за этот комментарий, @Alexandr Ciornii - ура!
Показать ещё 1 комментарий
1

Вот еще один пример Perl - без fork, с Time::HiRes с usleep (для основного) и setitimer (для вспомогательных) - однако, кажется, что setitimer нужно перезапустить - и даже то, похоже, просто запускать команды (на самом деле не ждать):

#!/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: Вот еще более простой пример с setitimer, который я не могу получить тайм-аут правильно (без ITIMER_VIRTUAL или ITIMER_REAL), он просто работает как можно быстрее:

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); 

Ещё вопросы

Сообщество Overcoder
Наверх
Меню