Нитевидные синхронизированные циклы в Perl - PullRequest
3 голосов
/ 23 сентября 2011

Я хотел бы, по сути, иметь высокоприоритетный поток, который запускается с заданным интервалом (здесь 0,5 мс) и прерывает «все», выполняет короткую задачу и затем возвращается в «спящий режим»; используя Ubuntu 11.04 и Perl v5.10.1. Проблема в том, что пока я получаю какие-то результаты, я не уверен, возможно ли получить «сжатые сроки».

Я сделал три тестовых сценария, в которых «цикл» в основном увеличивает счетчик в 10 раз, принимая временные метки, а затем он завершается, и временные метки печатаются (в микросекундах).

скрипт 1

Первый основан на фрагменте, который я нашел в Perl - Как вызвать событие после задержки - Perl - однако я не могу заставить этот конкретный фрагмент работать; поэтому с некоторыми изменениями это:

#!/usr/bin/env perl
# testloop01.pl

use strict;
use warnings;

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

my @tstamps;
my $cnt = 0;
my $numloops = 10;
my $loopperiod = 500e-6; # 0.000500 - 500 us

sub myfunc() {
  push(@tstamps, time);

  # repeat call for looping
  if ($cnt < $numloops) {
    $cnt++;
    $SIG{VTALRM} = &myfunc; # must have this!
    setitimer(ITIMER_VIRTUAL, 1, $loopperiod );
  }
}

# take first timestamp at start
push(@tstamps, time);

# start it off
#~ $SIG{VTALRM} = sub { print time, "\n"; }; # no work like this on Linux ?!
$SIG{VTALRM} = &myfunc;
setitimer(ITIMER_VIRTUAL, 1, $loopperiod );

# wait - sleep 2 s
Time::HiRes::sleep(2);

# output results
my ($firstts, $ts, $td);
$firstts = -1; # init
for(my $ix=0; $ix<scalar(@tstamps); $ix++) {
  $ts = $tstamps[$ix];
  if ($firstts == -1) { # $ix == 0
    $firstts = $ts;
    $td = 0;
  } else {  # $ix > 0
    $td = $ts - $tstamps[$ix-1];
  }
  printf "%10d (diff: %d)\n", ($ts-$firstts)*1e6, $td*1e6 ;
}

Выполнение этих отчетов:

$ ./testloop01.pl 
         0 (diff: 0)
        10 (diff: 10)
        25 (diff: 15)
        36 (diff: 10)
        46 (diff: 10)
        57 (diff: 10)
        66 (diff: 9)
        75 (diff: 8)
        83 (diff: 8)
        92 (diff: 9)
       102 (diff: 9)
       118 (diff: 15)

... означает, что циклы в основном работают так быстро, как могут, и запрошенное время не учитывается. Я предполагаю, что ITIMER_VIRTUAL не работает на моей машине.

сценарий 2

Второй скрипт основан на примере из Измерения на регулярных интервалах в Perl :

#!/usr/bin/env perl
# testloop02.pl

use strict;
use warnings;

use POSIX qw(pause);
# this does NOT work w/ ITIMER_VIRTUAL
use Time::HiRes qw(setitimer ITIMER_REAL time);

my @tstamps;
my $cnt = 0;
my $numloops = 10;
my $loopperiod = 500e-6; # 0.000500 - 500 us


# take first timestamp at start
push(@tstamps, time);


# how often do we trigger (seconds)?
my $first_interval = $loopperiod;
my $interval = $loopperiod;

# signal handler is empty
$SIG{ALRM} = sub { };

# first value is the initial wait, second is the wait thereafter
setitimer(ITIMER_REAL, $first_interval, $interval);

while (1) {

  # wait for alarm from timer
  pause;

  # do work that takes less than $interval to complete
  push(@tstamps, time);

  # repeat call for looping
  if ($cnt < $numloops) {
    $cnt++;
  } else {
    last;
  }

}

Time::HiRes::sleep(2); # helps avoid segfault, but doesn't seem to do anything;
                       # "it's apparently not safe to use sleep and a timer at
                       #   the same time, as one may reset the other"

# output results
my ($firstts, $ts, $td);
$firstts = -1; # init
for(my $ix=0; $ix<scalar(@tstamps); $ix++) {
  $ts = $tstamps[$ix];
  if ($firstts == -1) { # $ix == 0
    $firstts = $ts;
    $td = 0;
  } else {  # $ix > 0
    $td = $ts - $tstamps[$ix-1];
  }
  printf "%10d (diff: %d)\n", ($ts-$firstts)*1e6, $td*1e6 ;
}

Запуск результатов с:

$ ./testloop02.pl 
         0 (diff: 0)
       717 (diff: 717)
      1190 (diff: 473)
      1724 (diff: 534)
      2206 (diff: 481)
      2705 (diff: 499)
      3204 (diff: 499)
      3705 (diff: 500)
      4203 (diff: 498)
      4682 (diff: 478)
      5206 (diff: 524)
      5704 (diff: 498)

... что, как я полагаю, является настолько жестким из возможных временных интервалов (с «самоизмерением») на ПК, как этот. Однако проблема в том, что он работает в контексте одного потока (и usleep, по-видимому, больше не работает).

сценарий 3

Третий скрипт - это попытка сделать то же самое с потоками и usleep:

#!/usr/bin/env perl
# testloop03.pl

use strict;
use warnings;

use Time::HiRes qw ( usleep time );

use threads;
use threads::shared; # for shared variables

my @tstamps :shared;
my $cnt :shared = 0;
my $numloops :shared = 10;
my $loopperiod = 500e-6; # 0.000500 s - 500 us
my $loopperiodus :shared = $loopperiod*1e6; # 500 us

sub myfunc() {

  # repeat call for looping
  while ($cnt < $numloops) {
    push(@tstamps, time);
    $cnt++;
    usleep($loopperiodus);
  }
}

# take first timestamp at start
push(@tstamps, time);

# start it off
my $mthr = threads->create('myfunc');
$mthr->join();

# wait - sleep 2 s
Time::HiRes::sleep(2);

# output results
my ($firstts, $ts, $td);
$firstts = -1; # init
for(my $ix=0; $ix<scalar(@tstamps); $ix++) {
  $ts = $tstamps[$ix];
  if ($firstts == -1) { # $ix == 0
    $firstts = $ts;
    $td = 0;
  } else {  # $ix > 0
    $td = $ts - $tstamps[$ix-1];
  }
  printf "%10d (diff: %d)\n", ($ts-$firstts)*1e6, $td*1e6 ;
}

Когда я запускаю его, я получаю что-то вроде:

$ ./testloop03.pl 
         0 (diff: 0)
      7498 (diff: 7498)
      8569 (diff: 1070)
      9300 (diff: 731)
      9992 (diff: 691)
     10657 (diff: 664)
     11328 (diff: 671)
     11979 (diff: 650)
     12623 (diff: 643)
     13284 (diff: 661)
     13924 (diff: 639)

... что несколько близко, но немного отличается от требуемого периода - и я бы не назвал его таким же жестким, как второй сценарий (и на самом деле, я немного экспериментировал с этим, и мой опыт является то, что он может быть относительно быстро нестабильным - даже для довольно простых задач - в зависимости от давления со стороны ОС, таких как обновления графического интерфейса и тому подобное).

Так что мой вопрос - есть ли способ получить «жесткий» тайминг в Perl (как в примере 2, w / setitimer) - но в контексте потоков (как в примере 3; как я бы сказал) в основном хотите, чтобы другие вещи выполнялись в главном потоке, пока этот «временной цикл» спит)? К сожалению, пытаюсь отправить сигнал в ветку:

...
sub myfunc() {

  setitimer(ITIMER_REAL, $loopperiod, $loopperiod);

  # repeat call for looping
  while ($cnt < $numloops) {
    push(@tstamps, time);
    $cnt++;
    pause;
    # usleep($loopperiodus);
    # wait for alarm from timer
  }
}


# signal handler is empty
$SIG{ALRM} = sub { };

# take first timestamp at start
push(@tstamps, time);

# start it off
my $mthr = threads->create('myfunc');

# first value is the initial wait, second is the wait thereafter
#~ setitimer(ITIMER_REAL, $loopperiod, $loopperiod);

$mthr->join();
...

... не будет работать:

$ ./testloop04.pl 
Maximal count of pending signals (120) exceeded at ./testloop04.pl line 48.
Perl exited with active threads:
    1 running and unjoined
    -1 finished and unjoined
    0 running and detached

EDIT2: пример 2 можно использовать с fork, чтобы создать впечатление многопоточности ; однако, с разветвлением переменные не являются общими (и Не могу установить IPC: Shareable больше, что было бы самым легким выходом).

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

EDIT3: благодаря ответу от @ daxim , вот что выше с AnyEvent:

#!/usr/bin/env perl
# http://linux.die.net/man/3/anyevent
# http://search.cpan.org/~mlehmann/AnyEvent-6.02/lib/AnyEvent.pm

use 5.010;
use AnyEvent qw();

my @tstamps;
my $cnt = 0;
my $numloops = 10;
my $loopperiod = 500e-6; # 0.000500 - 500 us

my $result_ready = AnyEvent->condvar;

my %events = (
    timer => AE::timer(0, $loopperiod, sub {
      push(@tstamps, AE::time);
      if ($cnt < $numloops) {
        $cnt++;
      } else {
        #~ AE::cv->send; # doesn't exit loop?
        $result_ready->broadcast; # exits loop
      }
    }),
    #~ quit  => AE::cv->recv,
    quit  => $result_ready->wait,
);

sleep 1; # this will kick in only after loop is complete!

# output results
my ($firstts, $ts, $td);
$firstts = -1; # init
for(my $ix=0; $ix<scalar(@tstamps); $ix++) {
  $ts = $tstamps[$ix];
  if ($firstts == -1) { # $ix == 0
    $firstts = $ts;
    $td = 0;
  } else {  # $ix > 0
    $td = $ts - $tstamps[$ix-1];
  }
  printf "%10d (diff: %d)\n", ($ts-$firstts)*1e6, $td*1e6 ;
}

Обратите внимание, что на моей машине за 0,5 мс он дает несколько странные измерения (слева) - однако уже через 1,5 мс есть некоторые хорошие результаты (справа):

$ ./testloop05.pl 
         0 (diff: 0)             0 (diff: 0) 
        34 (diff: 34)           32 (diff: 32) 
       117 (diff: 82)         2152 (diff: 2120) 
      1665 (diff: 1548)       3597 (diff: 1445)   
      1691 (diff: 25)         5090 (diff: 1492) 
      3300 (diff: 1609)       6547 (diff: 1456)   
      3319 (diff: 18)         8090 (diff: 1542) 
      4970 (diff: 1651)       9592 (diff: 1502)   
      4990 (diff: 20)        11089 (diff: 1497) 
      6607 (diff: 1616)      12589 (diff: 1500)   
      6625 (diff: 18)        14091 (diff: 1501)    

1 Ответ

8 голосов
/ 23 сентября 2011

Потоки - не единственное средство мультипрограммирования.В мире Perl они одни из худших.Хотите попробовать свои силы в петлях событий?

use 5.010;
use AnyEvent qw();

my %events = (
    timer => AE::timer(0, 0.5, sub {
        $now = AE::time;
        say sprintf 'now: %f difference: %f', $now, $now - $previous;
        $previous = $now;
    }),
    quit  => AE::cv->recv,
);

$ perl testloop-ae.pl
now: 1316799028.264925 difference: 1316799028.264925
now: 1316799028.762484 difference: 0.497559
now: 1316799029.262058 difference: 0.499574
now: 1316799029.762640 difference: 0.500582
now: 1316799030.262207 difference: 0.499567
now: 1316799030.762668 difference: 0.500461
now: 1316799031.262242 difference: 0.499574
now: 1316799031.761805 difference: 0.499563
now: 1316799032.262378 difference: 0.500573
now: 1316799032.761953 difference: 0.499575
now: 1316799033.262513 difference: 0.500560
now: 1316799033.762081 difference: 0.499568
now: 1316799034.262674 difference: 0.500593
now: 1316799034.762256 difference: 0.499582
now: 1316799035.261837 difference: 0.499581
^C
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...