供应方法行为与水龙头之间的区别

jak*_*kar 7 tap act rakudo publish-subscribe raku

在 Raku 文档中关于供应方法法案(vs tap) https://docs.raku.org/type/Supply#method_act指出:

给定的代码保证一次只由一个线程执行

我的理解是一个线程必须在另一个线程必须运行它之前完成特定的代码对象。

如果是这种情况,我在尝试实现该功能时偶然发现了不同的行为。看看下面的代码片段,其中创建了 2 个“行为”并在不同的线程中运行:

#!/usr/bin/env perl6

say 'Main  runs in [ thread : ', +$*THREAD, ' ]';

my $b = 1;

sub actor {
    print "    Tap_$*tap                             : $^a  ", now;
    
    $*tap < 2 ??
                    do {
                        say " - Sleep 0.1";
                        sleep 0.1
                    }
              !!
                    do {
                        say " - Sleep 0.2";
                        sleep 0.2;
                    }
    
    $b++;
    say "    Tap_$*tap  +1 to \$b                     $b  ", now;
}

my $supply = supply {
    for 1..100 -> $i {
        say "For Tap_$*tap [ \$i = $i ] => About to emit : $b  ", now;
        emit $b;
        say "For Tap_$*tap [ \$i = $i ] =>       Emitted : $b  ", now;
        
        done if $b > 5
    }
}

start {
    my $*tap = 1;
    once say "Tap_$*tap runs in [ thread : {+$*THREAD} ]";
    $supply.act: &actor
}

start {
    my $*tap = 2;
    once say "Tap_$*tap runs in [ thread : {+$*THREAD} ]";
    $supply.act: &actor
}

sleep 1;
Run Code Online (Sandbox Code Playgroud)

结果如下(增加了时间间隔和评论):

  1   Main  runs in [ thread : 1 ]                                                       - Main thread              
  2   Tap_1 runs in [ thread : 4 ]                                                       - Tap 1 thread             
  3   For Tap_1 [ $i = 1 ] => About to emit : 1  Instant:1603354571.198187               - Supply thread [for tap 1]
  4       Tap_1                             : 1  Instant:1603354571.203074 - Sleep 0.1   - Tap 1 thread             
  5   Tap_2 runs in [ thread : 6 ]                                                       - Tap 2 thread             
  6   For Tap_2 [ $i = 1 ] => About to emit : 1  Instant:1603354571.213826               - Supply thread [for tap 2]
  7       Tap_2                             : 1  Instant:1603354571.213826 - Sleep 0.2   - Tap 2 thread             
  8                                                                                                                 
  9   -----------------------------------------------------------------------------------> Time +0.1 seconds        
 10                                                                                                                 
 11       Tap_1  +1 to $b                     2  Instant:1603354571.305723               - Tap 1 thread             
 12   For Tap_1 [ $i = 1 ] =>       Emitted : 2  Instant:1603354571.305723               - Supply thread [for tap 1]
 13   For Tap_1 [ $i = 2 ] => About to emit : 2  Instant:1603354571.30768                - Supply thread [for tap 1]
 14       Tap_1                             : 2  Instant:1603354571.30768  - Sleep 0.1   - Tap 1 thread             
 15                                                                                                                 
 16   -----------------------------------------------------------------------------------> Time +0.1 seconds        
 17                                                                                                                 
 18       Tap_1  +1 to $b                     3  Instant:1603354571.410354               - Tap 1 thread             
 19   For Tap_1 [ $i = 2 ] =>       Emitted : 4  Instant:1603354571.425018               - Supply thread [for tap 1]
 20       Tap_2  +1 to $b                     4  Instant:1603354571.425018               - Tap 2 thread             
 21   For Tap_1 [ $i = 3 ] => About to emit : 4  Instant:1603354571.425018               - Supply thread [for tap 1]
 22   For Tap_2 [ $i = 1 ] =>       Emitted : 4  Instant:1603354571.425995               - Supply thread [for tap 2]
 23       Tap_1                             : 4  Instant:1603354571.425995 - Sleep 0.1   - Tap 1 thread             
 24   For Tap_2 [ $i = 2 ] => About to emit : 4  Instant:1603354571.425995               - Supply thread [for tap 2]
 25       Tap_2                             : 4  Instant:1603354571.426973 - Sleep 0.2   - Tap 2 thread             
 26                                                                                                                 
 27   -----------------------------------------------------------------------------------> Time +0.1 seconds        
 28                                                                                                                 
 29       Tap_1  +1 to $b                     5  Instant:1603354571.528079               - Tap 1 thread             
 30   For Tap_1 [ $i = 3 ] =>       Emitted : 5  Instant:1603354571.52906                - Supply thread [for tap 1]
 31   For Tap_1 [ $i = 4 ] => About to emit : 5  Instant:1603354571.52906                - Supply thread [for tap 1]
 32       Tap_1                             : 5  Instant:1603354571.53004  - Sleep 0.1   - Tap 1 thread             
 33                                                                                                                 
 34   -----------------------------------------------------------------------------------> Time +0.1 seconds        
 35                                                                                                                 
 36       Tap_2  +1 to $b                     6  Instant:1603354571.62859                - Tap 2 thread             
 37   For Tap_2 [ $i = 2 ] =>       Emitted : 6  Instant:1603354571.62859                - Supply thread [for tap 2]
 38       Tap_1  +1 to $b                     7  Instant:1603354571.631512               - Tap 1 thread             
 39   For Tap_1 [ $i = 4 ] =>       Emitted : 7  Instant:1603354571.631512               - Supply thread [for tap 2]

Run Code Online (Sandbox Code Playgroud)

可以很容易地观察到代码对象(子例程&actor)在 2 个线程中同时运行(例如,请参见输出第 4 行和第 7 行)。

有人可以澄清我对此事的误解吗?

Jon*_*ton 12

Raku 的日常使用tapact日常使用之间几乎没有任何区别,因为Supply您遇到的几乎所有产品都是串行电源。串行供应是一种已经强制执行的协议,即在处理前一个值之前不会发出值。的实现act是:

method act(Supply:D: &actor, *%others) {
    self.sanitize.tap(&actor, |%others)
}
Run Code Online (Sandbox Code Playgroud)

Wheresanitize强制执行值的串行发射,此外还确保事件遵循语法emit* [done | quit]。由于这些属性通常是非常需要的,因此获取 a 的所有内置方法都Supply提供了它们,除了能够创建 aSupplier并调用unsanitized-supply它。(历史记录:一个非常早期的原型并没有如此广泛地强制执行这些属性,从而更需要一种方法来做act它所做的事情。虽然随着设计涉及最终在第一个语言版本中发布的内容,对它的需求减少了,它必须保留其漂亮的短名称。)

误解源于期望事件的序列化是每个源,而实际上它是每个订阅。考虑这个例子:

my $timer = Supply.interval(1);
$timer.tap: { say "A: {now}" };
$timer.tap: { say "B: {now}" };
sleep 5;
Run Code Online (Sandbox Code Playgroud)

它产生这样的输出:

A: Instant:1603364746.02766
B: Instant:1603364746.031255
A: Instant:1603364747.025255
B: Instant:1603364747.028305
A: Instant:1603364748.025584
B: Instant:1603364748.029797
A: Instant:1603364749.026596
B: Instant:1603364749.029643
A: Instant:1603364750.027881
B: Instant:1603364750.030851
A: Instant:1603364751.030137
Run Code Online (Sandbox Code Playgroud)

有一个事件源,但我们为它建立了两个订阅。每个订阅都强制执行串行规则,所以如果我们这样做:

my $timer = Supply.interval(1);
$timer.tap: { sleep 1.5; say "A: {now}" };
$timer.tap: { sleep 1.5; say "B: {now}" };
sleep 5;
Run Code Online (Sandbox Code Playgroud)

然后我们观察到以下输出:

A: Instant:1603364909.442341
B: Instant:1603364909.481506
A: Instant:1603364910.950359
B: Instant:1603364910.982771
A: Instant:1603364912.451916
B: Instant:1603364912.485064
Run Code Online (Sandbox Code Playgroud)

显示每个订阅一次获取一个事件,但仅共享(按需)源不会产生任何共享背压。

由于并发控制与订阅相关联,因此将相同的闭包克隆传递给tap/是无关紧要的act。执行跨多个订阅并发控制被的境界supply/ react/ whenever。例如这个:

my $timer = Supply.interval(1);
react {
    whenever $timer {
        sleep 1.5;
        say "A: {now}"
    }
    whenever $timer {
        sleep 1.5;
        say "B: {now}"
    }
}
Run Code Online (Sandbox Code Playgroud)

给出这样的输出:

A: Instant:1603365363.872672
B: Instant:1603365365.379991
A: Instant:1603365366.882114
B: Instant:1603365368.383392
A: Instant:1603365369.884608
B: Instant:1603365371.386087
Run Code Online (Sandbox Code Playgroud)

由于react块隐含的并发控制,每个事件相隔 1.5 秒。