Я хотел бы использовать 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, - использовать 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 в вашем примере выше. Причина, по которой я буду использовать его, чтобы сделать что-то вроде ваших периодических задач выше, заключается в том, что он дает инструменты, позволяющие легко добавлять столько сложных периодов, сколько вам захочется.
Он также предлагает больше инструментов для планирования вызовов задач, которые могут показаться вам интересными.
Простая реализация 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 (там еще немало).
gevent
также ... Ура!
Для 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();
}
Вот еще один пример 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);
twisted
, хорошей ссылке тоже ... Ура!