Выпуск 26. Апрель 2015
← Работа с WebSocket в Perl | Содержание | Грамматики в Perl 6 →Промисы в Perl 6
Вторая часть обзора возможностей Perl 6 для параллельных и конкуррентных вычислений
Промисы (promises, обещания) — это объекты, помогающие синхронизировать разные части параллельных процессов. Простейшее использование таких объектов — уведомить или узнать о том, сдержано ли данное ранее обещание, нарушено ли оно или пока неизвестно.
Базовые возможности
Объект создается вызововом Promise.new
, а статус обещания доступен в методе status
. Пока никаких действий не выполнено, промис находится в состоянии Planned
:
my $p = Promise.new;
say $p.status; # Planned
Метод keep
переводит обещание в статус сдержанного (Kept
):
$p.keep;
say $p.status; # Kept
Аналогично, обещание можно нарушить, вызвав метод break
:
my $p1 = Promise.new;
say $p1.status; # Planned
$p1.break;
say $p1.status; # Broken
Вместо вызова метода status
возможно преобразовать объект типа Promise
в булеву величину, вызвав метод Bool
или воспользовавшись унарным оператором ?
:
say $p.Bool;
say ?$p;
Обратите внимание, что в этом случае возвращается только статус обещания (то есть либо еще не известно, сдержано оно или нарушено, либо уже известно), но не его результат.
Результат сообщает метод result
, но с ним нужно быть осторожным. Если обещание сдержано, result
возвращает истину:
my $p = Promise.new;
$p.keep;
say $p.result; # True
Обращение к result
блокирует программу до тех пор, пока обещание не выйдет из статуса Planned
.
Если обещание нарушено:
my $p = Promise.new;
$p.break;
say $p.result;
то при вызове метода result
возникает исключение:
$ perl6 promise3.pl
False
in method result at src/gen/m-CORE.setting:23096
in block <unit> at promise3.pl:3
Исключения возможно избежать, спрятав вызов внутрь блока try
(но say
тоже ничего не напечатает):
my $p = Promise.new;
$p.break;
try {
say $p.result;
}
Для нарушенных обещаний детали исключения можно получить, вызвав метод cause
вместо result
.
При вызове keep
или break
возможно передать параметр с сообщением, которое может быть или текстом, или объектом. В этом случае при вызове result
вместо True
и False
(внутри исключения) будет возвращено переданное сообщение.
Фабричные методы
В классе Promise
определены несколько интересных методов-фабрик, создающих промисы.
start
Метод start
создает промис, внутри которого выполняется блок кода. Вместо явного вызова метода на классе Promise.start
удобнее воспользоваться одноименным предопределенным ключевым словом:
my $p = start {
42
}
(Точка с запятой после закрывающей скобки необязательна даже если после нее есть другие инструкции.)
Результат выполнения кода будет результатом промиса. Если код привел к исключению, промис окажется нарушенным.
my $p = start {
42
}
say $p.result; # 42
say $p.status; # Kept
Важно понимать, что создание блока start
еще не означает, что код из него выполнен. Метод start
сразу возвращает управление, поэтому если тут же попытаться узнать статус промиса, то результат может оказаться неверным. В предыдущем примере вызов $p.result
блокировал выполнение программы до того момента, когда код из блока полностью выполнится, соответственно, результат будет содержать сообщение, которое вернул блок, а статус изменится в состояние Kept
.
Если поменять местами последние две строки, результат может оказаться другим. Для более воспроизводимого результата можно добавить паузу внутри блока:
my $p = start {
sleep 1;
42
}
say $p.status; # Planned
say $p.result; # 42
say $p.status; # Kept
Первый вызов $p.status
происходит сразу после создания блока с промисом, и поэтому он еще не выполнен, так что статус оказывается Planned
. А второй вывзов происходит уже после того, как метод $p.result
дождался выполнения блока кода.
Если же блок кода вызвал исключение, то промис окажется нарушенным:
my $p = start {
die;
}
try {
say $p.result;
}
say $p.status; # Эта строка выполнится и напечатает Broken
Вторая ловушка с блоком start
— важно понимать, что именно вызывает исключение. Например, попытка деления на ноль приведет к исключению только в том случае, когда случится попытка воспользоваться результатом (это называется soft failure), а до тех пор Perl 6 удовлетворится тем, что результат деления на ноль — значение типа Rat
.
# $p1 будет Kept
my $p1 = start {
my $inf = 1 / 0;
}
# $p2 окажется Broken
my $p2 = start {
my $inf = 1 / 0;
say $inf;
}
in и at
Методы Promise.in
и Promise.at
создают промисы, которые будут сдержаны через заданное число секунд или к указанному времени.
Например:
my $p = Promise.in(3);
for 1..5 {
say $p.status;
sleep 1;
}
Эта программа напечатает следующее (то есть промис оказывается выполненным через три секунды):
Planned
Planned
Planned
Kept
Kept
anyof и allof
Методы Promise.anyof
и Promise.allof
создают новые промисы, которые будут сдержаны только тогда, когда будет сдержан хотя бы один из (для anyof
) или все (для allof
) промисы, указанные при создании.
Один из полезных вариантов применения, приведенный в документации, — реализация завершения по таймауту для вычислений, котоыре могут занять длительное время:
my $code = start {
sleep 5
}
my $timeout = Promise.in(3);
my $done = Promise.anyof($code, $timeout);
say $done.result;
Этот код в теории должен завершиться после истечения таймаута в три секунды: в этот момент промис $timeout
окажется выполненным, поэтому выполненным сразу же станет и промис $done
.
В Rakudo Start 2015.03 этот код работает не как ожидается (он ждет пять секунд и сообщает о том, что $code
выполнен), и на IRC-канале предложили обходное решение вместо Promise.in(3)
:
my $timeout = start {
sleep 3
}
then
Метод then
, вызванный на уже определенном промисе, создает еще один, код которого будет вызван после того, как исходный промис будет сдержан или нарушен.
my $p = Promise.in(2);
my $t = $p.then({say "OK"}); # Напечатается через две секунды
say "promissed"; # Печатается сразу
sleep 3;
say "done";
Другой пример, в котором обещание сдержать не удалось:
Promise.start({ # Новый промис.
say 1 / 0 # Исключение.
}).then({ # Код, выполняемый после поломки.
say "oops"
}).result # Необходимо, чтобы дождаться выполнения.
Пример
В заключение приведу пример реализации сотрировки типа sleep sort на промисах.
my @promises;
for @*ARGS -> $a {
@promises.push(start {
sleep $a;
say $a;
})
}
await(|@promises);
Программа принимает значения с командной строки:
$ perl6 sleep-sort.pl 3 7 4 9 1 6 2 5
и создает для каждого из них промис, печатающий значение после необходимой задержки. В последней строке вызвана команда await
, которая (аналогично методу Promise.allof
) будет дожидаться выполнения всех промисов.
Продолжение следует
В следующих выпусках журнала я хочу рассказать о других возможностях Perl 6 для параллельной обработки. А пока предлагаю посмотреть доклад Патрика Мишо Parallelism in Perl 6.
← Работа с WebSocket в Perl | Содержание | Грамматики в Perl 6 →