Raku эквивалентно методу setTimeout (fn, 0) в JavaScript?

Цикл событий JavaScript использует очередь сообщений для планирования работы и выполняет каждое сообщение до завершения перед запуском следующего. В результате нишевый, но неожиданно распространенный шаблон в коде JavaScript состоит в том, чтобы запланировать выполнение функции после того, как сообщения, находящиеся в данный момент в очереди, были обработаны с использованием setTimeout(fn, 0). Например:

setTimeout(() => {console.log('first')}, 0);
console.log('second'); 
// OUTPUT: "second\nfirst"

(дополнительные сведения см. в описании MDN.)

Предлагает ли Raku аналогичный способ составить график работы сразу после того, как все запланированные на данный момент работы будут завершены? На основе моего понимания модели параллелизма Раку (в основном только из этот пост 6guts), похоже, что Raku использует аналогичную очередь сообщений (хотя, пожалуйста, поправьте меня, если это не так!). Сначала я подумал, что Promise.in(0).then: &fn был прямым эквивалентом:

my $p = Promise.in(0).then: { say 'first' }
say 'second';
await $p;
# OUTPUT: «second\nfirst» # ...usually

Однако после многократного выполнения приведенного выше кода я понял, что он просто устанавливает условие гонки и 'first' иногда первым. Итак, есть ли какой-нибудь код Raku, который обеспечивает такое же поведение? И если да, то является ли такое поведение следствием преднамеренной семантики, выбранной Raku / Roast, а не результатом (возможно, временных) деталей реализации?


person codesections    schedule 24.05.2021    source источник


Ответы (2)


Неупорядоченный

У Raku нет упорядоченной очереди сообщений. В нем есть неупорядоченный список того, что нужно сделать.

# schedule them to run at the same second
# just to make it more likely that they will be out of order
my $wait = now + 1;

my @run;
for 1..20 -> $n {
  push @run, Promise.at($wait).then: {say $n}
}
await @run;

Это могло напечатать числа в любом порядке.

1
2
3
4
5
6
7
8
11
12
13
14
15
16
17
18
9
10
19
20

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

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

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

my @run;
for 1..20 -> $n {
    push @run, start {say $n}
};
await @run;

Вы можете запустить это сотни раз, прежде чем он начнет печатать что-то не в порядке, но в конечном итоге это произойдет.

Даже если вы перешли на низкий уровень и использовали $*SCHEDULER.cue, нет гарантии, что он будет работать после всего остального.

my @nums;
for 1..100 -> $n {
    $*SCHEDULER.cue: {push @nums, $n; say $n}
}
say @nums;

Мало того, что он может выйти из строя, массив @nums, вероятно, не будет иметь всех значений, потому что каждый поток может сбивать с толку то, что делает другой поток.

Под капотом методы Promise, которые планируют выполнение чего-либо, в конечном итоге каким-то образом вызывают $*SCHEDULER.cue.

Запланируйте что-нибудь еще

Вы можете сказать Raku запустить ваш код после чего-то еще.

my $p = Promise.in(1);
my $p2 = $p.then: {say 'first'}
my $p3 = $p.then: {say 'second'}
react {
  whenever start say('first') {
    whenever start say('second') {
    }
  }
}

Однако вам нужно иметь ссылку на эту вещь.

Медленный

Если бы у Raku был способ запускать что-то после текущих запланированных событий, то ему пришлось бы отслеживать, что выполняется, и следить за тем, чтобы ваш код не запускался до тех пор, пока они не будут завершены.

my $a = start {

    # pointless busy-work that takes two seconds
    my $wait = now + 2;
    my $n = 0;
    while now ≤ $wait {
        $n++
    }
    say $n; # make sure the loop doesn't get optimized away

    say 'first';
}

my $b = start say 'second';

await $a, $b;
second
1427387
first

Если это обеспечит запуск $b после $a, то на $b в течение двух целых секунд не будет выполняться никакая работа.

Вместо этого он просто заставляет $b работать в другом потоке, потому что тот, который имеет дело с $a, в настоящее время занят.

Это хорошо, потому что что, если $b тоже будет медленным. Мы бы запланировали две медленные вещи, которые будут выполняться последовательно, а не параллельно.

Javascript

Я думаю, что единственная причина, по которой он в настоящее время работает в Javascript, заключается в том, что он, похоже, не использует преимущества нескольких ядер процессора. Или в нем что-то вроде GIL.

Я написал код Raku, в котором мой 4-ядерный процессор загружен на 500%. (Гиперпоточный процессор Intel, где одно ядро ​​выглядит как 2 ядра)
Я не уверен, что вы можете сделать то же самое с одной программой Javascript.

person Brad Gilbert    schedule 25.05.2021
comment
Javascript однопоточный. Так что нет. - person jjmerelo; 25.05.2021

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

# Subclass Channel for type safety.
class MessageQueue is Channel {
    method send(&code) { nextsame }
    method run { while self.poll -> &code { &code.() } }
}

# Our queue
my MessageQueue \message-queue .= new;

# Schedule everything with the queue, just for fun.
message-queue.send: {
    # We can schedule code to run within scheduled code
    message-queue.send: { say ‘first’ };
    
    say ‘second’;
    
    # Demonstrating type checking in the send call
    try { message-queue.send: ‘Hello’; } or warn $!;
}

message-queue.run;

Ради интереса я создал PoC Scheduler, который позволяет вам использовать запускать задачи через однопоточный канал, используя Promise.(in|at|start), см. https://glot.io/snippets/fzbwj8me8w

person Maniacs Thrift Jewels    schedule 07.06.2021