Использование readline
(он же <>
) совершенно неверно по двум причинам: оно буферизовано и блокируется.
Буферизация плохая
Точнее, буферизация с использованиембуферы, которые не могут быть проверены, являются плохими.
Система может выполнять всю необходимую буферизацию, поскольку вы можете просматривать ее буферы, используя select
.
Системе ввода-вывода Perl нельзя разрешать делатьлюбая буферизация, потому что вы не можете заглянуть в ее буферы.
Давайте рассмотрим пример того, что может произойти с использованием readline
в цикле select
.
"abc\ndef\n"
поступает вдескриптор. select
уведомляет вас о том, что есть данные для чтения. readline
попытается прочитать фрагмент из дескриптора. "abc\ndef\n"
будетбудет помещен в буфер Perl для дескриптора. readline
вернет "abc\n"
.
В этот момент вы снова вызываете select
и хотите, чтобы он позволилВы знаете, что есть еще что почитать ("def\n"
).Однако select
сообщит, что читать нечего, поскольку select
является системным вызовом, а данные уже были прочитаны из системы.Это означает, что вам придется подождать, пока придет больше, прежде чем вы сможете прочитать "def\n"
.
Следующая программа иллюстрирует это:
use IO::Select qw( );
use IO::Handle qw( );
sub producer {
my ($fh) = @_;
for (;;) {
print($fh time(), "\n") or die;
print($fh time(), "\n") or die;
sleep(3);
}
}
sub consumer {
my ($fh) = @_;
my $sel = IO::Select->new($fh);
while ($sel->can_read()) {
my $got = <$fh>;
last if !defined($got);
chomp $got;
print("It took ", (time()-$got), " seconds to get the msg\n");
}
}
pipe(my $rfh, my $wfh) or die;
$wfh->autoflush(1);
fork() ? producer($wfh) : consumer($rfh);
Вывод:
It took 0 seconds to get the msg
It took 3 seconds to get the msg
It took 0 seconds to get the msg
It took 3 seconds to get the msg
It took 0 seconds to get the msg
...
Это можно исправить с помощью небуферизованного ввода-вывода:
sub consumer {
my ($fh) = @_;
my $sel = IO::Select->new($fh);
my $buf = '';
while ($sel->can_read()) {
sysread($fh, $buf, 64*1024, length($buf)) or last;
while ( my ($got) = $buf =~ s/^(.*)\n// ) {
print("It took ", (time()-$got), " seconds to get the msg\n");
}
}
}
Вывод:
It took 0 seconds to get the msg
It took 0 seconds to get the msg
It took 0 seconds to get the msg
It took 0 seconds to get the msg
It took 0 seconds to get the msg
It took 0 seconds to get the msg
...
Блокировка плохая
Давайте рассмотрим примеро том, что может произойти при использовании readline
в цикле select
.
"abc\ndef\n"
поступает на дескриптор. select
уведомляет вас о том, что есть данные для чтения. readline
попытается прочитать чанк из сокета. "abc\ndef\n"
будет помещен в буфер Perl для дескриптора. readline
не имеетполучил новую строку, поэтому он пытается прочитать другой фрагмент из сокета. - В настоящее время больше нет доступных данных, поэтому он блокируется.
Это противоречит цели использования select
.
[Ожидается демонстрационный код]
Решение
Вы должны реализовать версию readline
, которая не блокирует и использует только bвы можете проверить.Вторая часть проста, потому что вы можете проверить созданные вами буферы.
- Создать буфер для каждого дескриптора.
- Когда данные поступают из дескриптора, прочитайте их, но не более.Когда данные ожидают (как мы знаем из
select
), sysread
вернет то, что доступно, не дожидаясь поступления большего количества.Это делает sysread
идеальным для этой задачи. - Добавляет считанные данные в соответствующий буфер.
- Для каждого полного сообщения в буфере извлекает его и обрабатывает его.
Добавление дескриптора:
$select->add($fh);
$clients{fileno($fh)} = {
buf => '',
...
};
select
loop:
while (my @ready = $select->can_read) {
for my $fh (@ready) {
my $client = $clients{fileno($fh)};
our $buf; local *buf = \($client->{buf}); # alias $buf = $client->{buf};
my $rv = sysread($fh, $buf, 64*1024, length($buf));
if (!$rv) {
if (!defined($rv)) {
... # Handle error
}
elsif (length($buf)) {
... # Handle eof with partial message
}
else {
... # Handle eof
}
delete $clients{fileno($fh)};
$sel->remove($fh);
next;
}
while ( my ($msg) = $buf =~ s/^(.*)\n// )
... # Process message.
}
}
}
Кстати, это гораздо проще сделать с помощью потоков, и это даже неОбращайтесь к авторам!
Обратите внимание, что IPC :: Run может выполнить всю тяжелую работу за вас, и что асинхронный ввод-вывод может использоваться в качестве альтернативы select
.