Raku 相当于 JavaScript 的 `setTimeout(fn, 0)`?

Raku equivalent to JavaScript's `setTimeout(fn, 0)`?

JavaScript 的事件循环使用消息队列来安排工作,并且 运行 在开始下一条消息之前完成每条消息。因此,JavaScript 代码中的一个小众但令人惊讶的常见模式是在使用 setTimeout(fn, 0) 处理当前队列中的消息后将函数调度到 运行。例如:

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

(有关详细信息,请参阅 MDN's description。)

Raku 是否提供任何类似的方式来在所有当前安排的工作完成后立即安排工作?根据我对 Raku 的并发模型的理解(主要来自 this 6guts post),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 决定的有意语义的结果,而不是(可能是临时的)实现细节的结果吗?

未排序

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 实际上是多线程的。如果你给它足够的工作,它将使用你所有的 cpu 核心。

这意味着在当前队列中的所有内容完成后,永远无法说 运行

即使我只是使用 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 确实有办法在当前安排的事件之后 运行 事情,那么它必须跟踪正在发生的事情 运行ning 并确保您的代码不会 运行 直到他们完成。

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 中工作的唯一原因是因为它似乎没有利用多个 cpu 核心。或者它有类似 GIL 的东西。

我编写的 Raku 代码使我的 4 核 CPU 利用率达到 500%。 (Intel超线程cpu,其中1核好像是2核)
我不确定您是否可以对单个 Javascript 程序执行相同的操作。

可以使用Channel以更明确的方式做类似的事情:

# 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 调度程序,它允许您使用 Promise.(in|at|start) 通过单线程通道使用 运行 个任务,请参阅 https://glot.io/snippets/fzbwj8me8w