【问题标题】:Raku equivalent to JavaScript's `setTimeout(fn, 0)`?Raku 相当于 JavaScript 的`setTimeout(fn, 0)`?
【发布时间】:2021-08-13 04:28:04
【问题描述】:

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

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

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

Raku's 是否提供任何类似的方式来在所有当前安排的工作完成后立即安排工作?根据我对 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' 是 sometimes 首先。那么,是否有任何 Raku 代码可以提供相同的行为?如果是这样,这种行为是否是 Raku/Roast 决定的有意语义的结果,而不是(可能是临时的)实现细节的结果?

【问题讨论】:

    标签: javascript message-queue raku event-loop rakudo


    【解决方案1】:

    无序

    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%。 (英特尔超线程 CPU,其中一个内核似乎是 2 个内核)
    我不确定您是否可以对单个 Javascript 程序执行相同的操作。

    【讨论】:

      【解决方案2】:

      您可以使用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

      【讨论】:

        猜你喜欢
        • 2014-07-29
        • 1970-01-01
        • 1970-01-01
        • 2016-02-14
        • 1970-01-01
        • 1970-01-01
        • 2016-11-18
        • 2011-06-16
        • 2022-12-01
        相关资源
        最近更新 更多