Raku中相当于JavaScript的`setTimeout(fn, 0)`的方法是什么?

9

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

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

(更多细节请参见MDN的说明。)
Raku是否提供任何类似的方法,以便在所有当前计划的工作完成后立即安排工作?根据我对Raku并发模型的理解(主要来自这篇6guts文章),它似乎使用类似的消息队列(如果我错了,请纠正我!)。我最初认为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故意决定的语义结果,而不是(可能是临时的)实现细节的结果吗?
2个回答

12

无序

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将有两秒钟的空闲时间。相反,它会在另一个线程上运行,因为处理$a的线程目前正忙。这是一件好事,因为如果$b也很慢,我们就会安排两个缓慢的任务按顺序而不是并行运行。

Javascript

我认为它目前在Javascript中可行的唯一原因是它似乎没有利用多个CPU核心,或者它具有类似于GIL的东西。我编写了Raku代码,使我的4核CPU利用率达到500%(Intel超线程CPU,其中一个核心似乎是2个核心)。我不确定单个Javascript程序是否能做到同样的效果。

1

您可以使用频道以更明确的方式执行类似操作:

# 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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接