Supply method act 与 tap 之间的区别
Difference between Supply method act vs tap
在 Raku 文档中关于 Supply 方法 act(相对于 tap)
https://docs.raku.org/type/Supply#method_act 表示:
the given code is guaranteed to be executed by only one thread at a
time
我的理解是,一个线程必须先完成特定代码对象,然后另一个线程才能 运行 它。
如果是这样的话,我在尝试实现该功能时偶然发现了不同的行为。看一下下面的代码片段,其中创建了 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;
结果如下(添加了时间间隔和注释):
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]
可以很容易地观察到代码对象(子例程 &actor)在 2 个线程中并发 运行ning(例如,参见输出行 4 和 7)。
谁能澄清我对此事的误解?
在 Raku 的日常使用中,tap
和 act
之间几乎没有任何区别,因为您遇到的几乎每个 Supply
都是 串行电源。串行供应是一种已经强制执行协议的供应,即在处理完前一个值之前不会发出一个值。 act
的实现是:
method act(Supply:D: &actor, *%others) {
self.sanitize.tap(&actor, |%others)
}
其中 sanitize
强制值的连续发射,此外确保事件遵循语法 emit* [done | quit]
。由于这些属性通常是非常需要的,每个 built-in 获得 Supply
的方法都提供了它们,除了能够创建 Supplier
并在其上调用 unsanitized-supply
. (历史记录:一个非常早期的原型并没有如此广泛地强制执行这些属性,因此更需要一种方法来完成 act
所做的事情。虽然随着设计涉及到最终交付的内容,对它的需求减少了第一个语言版本,它必须保留其漂亮的简称。)
产生误解的原因是期望事件的序列化是 每个源,而实际上它是 每个订阅。考虑这个例子:
my $timer = Supply.interval(1);
$timer.tap: { say "A: {now}" };
$timer.tap: { say "B: {now}" };
sleep 5;
产生这样的输出:
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
事件源只有一个,但我们为其建立了两个订阅。每个订阅都强制执行序列规则,所以如果我们这样做:
my $timer = Supply.interval(1);
$timer.tap: { sleep 1.5; say "A: {now}" };
$timer.tap: { sleep 1.5; say "B: {now}" };
sleep 5;
然后我们观察到如下输出:
A: Instant:1603364909.442341
B: Instant:1603364909.481506
A: Instant:1603364910.950359
B: Instant:1603364910.982771
A: Instant:1603364912.451916
B: Instant:1603364912.485064
显示每个订阅一次获得一个事件,但仅仅共享一个 (on-demand) 源不会产生任何共享背压。
由于并发控制与 订阅 关联,如果将相同的闭包克隆传递给 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}"
}
}
给出这样的输出:
A: Instant:1603365363.872672
B: Instant:1603365365.379991
A: Instant:1603365366.882114
B: Instant:1603365368.383392
A: Instant:1603365369.884608
B: Instant:1603365371.386087
由于 react
块隐含的并发控制,每个事件相隔 1.5 秒。
在 Raku 文档中关于 Supply 方法 act(相对于 tap) https://docs.raku.org/type/Supply#method_act 表示:
the given code is guaranteed to be executed by only one thread at a time
我的理解是,一个线程必须先完成特定代码对象,然后另一个线程才能 运行 它。
如果是这样的话,我在尝试实现该功能时偶然发现了不同的行为。看一下下面的代码片段,其中创建了 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;
结果如下(添加了时间间隔和注释):
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]
可以很容易地观察到代码对象(子例程 &actor)在 2 个线程中并发 运行ning(例如,参见输出行 4 和 7)。
谁能澄清我对此事的误解?
在 Raku 的日常使用中,tap
和 act
之间几乎没有任何区别,因为您遇到的几乎每个 Supply
都是 串行电源。串行供应是一种已经强制执行协议的供应,即在处理完前一个值之前不会发出一个值。 act
的实现是:
method act(Supply:D: &actor, *%others) {
self.sanitize.tap(&actor, |%others)
}
其中 sanitize
强制值的连续发射,此外确保事件遵循语法 emit* [done | quit]
。由于这些属性通常是非常需要的,每个 built-in 获得 Supply
的方法都提供了它们,除了能够创建 Supplier
并在其上调用 unsanitized-supply
. (历史记录:一个非常早期的原型并没有如此广泛地强制执行这些属性,因此更需要一种方法来完成 act
所做的事情。虽然随着设计涉及到最终交付的内容,对它的需求减少了第一个语言版本,它必须保留其漂亮的简称。)
产生误解的原因是期望事件的序列化是 每个源,而实际上它是 每个订阅。考虑这个例子:
my $timer = Supply.interval(1);
$timer.tap: { say "A: {now}" };
$timer.tap: { say "B: {now}" };
sleep 5;
产生这样的输出:
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
事件源只有一个,但我们为其建立了两个订阅。每个订阅都强制执行序列规则,所以如果我们这样做:
my $timer = Supply.interval(1);
$timer.tap: { sleep 1.5; say "A: {now}" };
$timer.tap: { sleep 1.5; say "B: {now}" };
sleep 5;
然后我们观察到如下输出:
A: Instant:1603364909.442341
B: Instant:1603364909.481506
A: Instant:1603364910.950359
B: Instant:1603364910.982771
A: Instant:1603364912.451916
B: Instant:1603364912.485064
显示每个订阅一次获得一个事件,但仅仅共享一个 (on-demand) 源不会产生任何共享背压。
由于并发控制与 订阅 关联,如果将相同的闭包克隆传递给 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}"
}
}
给出这样的输出:
A: Instant:1603365363.872672
B: Instant:1603365365.379991
A: Instant:1603365366.882114
B: Instant:1603365368.383392
A: Instant:1603365369.884608
B: Instant:1603365371.386087
由于 react
块隐含的并发控制,每个事件相隔 1.5 秒。