Raku 等同于 JavaScript 的 `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 确实有办法在当前计划的事件之后运行,那么它必须跟踪正在运行的内容并确保您的代码在它们完成后才运行。

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

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...