Ping Pang in Raku
使用 channels 在线程之间传递消息是一种管理并发的简单方法。
一位朋友最近向我展示了一个很酷的程序,用 elixir 来演示消息传递 - 两个线程通过来回传递递增的数字来计数。快速搜索出现了一些例子。
我决定在 Raku 中实现这个。这就是我想出来的→
my ($ping, $pong) = Channel.new xx 2;
sub ping {
while $ping.receive -> $n {
say "ping $n (thread #{$*THREAD.id})";
$pong.send: $n + 1;
}
}
sub pong {
while $pong.receive -> $n {
last if $n >= 5;
say "pong $n (thread #{$*THREAD.id})";
$ping.send: $n + 1;
}
}
$ping.send: 1;
await Promise.anyof(
(start ping),
(start pong)
);
$ping
和 $pong
都是 channel。子程序 ping 和 pong 从它们相应的 channel 接收并发送到另一个。
如果你运行它,你会看到从 1 到 5 的数字。
ping 1 (thread #3)
pong 2 (thread #4)
ping 3 (thread #3)
pong 4 (thread #4)
ping 5 (thread #3)
接下来我想封装一些常见的行为。这是一个发送和接收的乒乓球“玩家” - 所以让我们创建一个 Player 类。
我想 “ping” 和 “pong” 是每个玩家的球拍发出的声音,因为他们击球,所以我添加了一个 $.sound
属性。并且 $.opponent
属性代表每个玩家的对手。
$.channel
是处理(即方法委派)发送和接收方法的另一个属性。
class Player {
has $.sound;
has $.opponent is rw;
has $.channel handles <send receive> = Channel.new;
has $.promise;
method play {
$!promise =
start {
while self.receive -> $n {
last if $n == 6;
say "$.sound $n (thread #{$*THREAD.id})";
$.opponent.send: $n + 1;
}
}
}
}
my ($ping,$pong) = <ping pong>.map: { Player.new(:$^sound) }
($ping, $pong)>>.opponent = ($pong, $ping);
($ping, $pong)>>.play;
$ping.send: 1;
await $pong.promise;
运行这个给我们类似于最后一个:
ping 1 (thread #3)
pong 2 (thread #4)
ping 3 (thread #3)
pong 4 (thread #4)
ping 5 (thread #3)
现在让我们更像乒乓球:
- 有四分之一的机会错过球。
- 第一个到第一个胜利,你必须赢两个。
- 每五点,切换谁服务。
class Player {
has $.sound handles 'Str';
has $.opponent is rw;
has $.channel handles <send receive> = Channel.new;
has $.promise;
method missed {
$.promise.status == Kept;
}
method play {
$!promise = start {
while self.receive -> $n {
last if (^4).pick==1;
print "{$.sound} $n ";
self.opponent.send: $n + 1;
}
}
}
}
my @sounds = <ping pong>;
my @players = @sounds.map: { Player.new(:$^sound) }
@players».opponent «=» @players.rotate;
my $serving = @players[0];
my %score = @players Z=> 0 xx *;
@players».play;
repeat {
$serving.send(1);
await Promise.anyof(@players».promise);
with @players.first({ .missed }) -> $missed {
print "Miss by $missed.";
%score{$missed.opponent}++;
$missed.play;
}
say " Score: " ~ %score{@sounds}.join(' to ');
if %score.values.sum %% 5 {
$serving .= opponent;
say "--Now serving: $serving--";
}
} until %score.values.any >= 11 and (abs [-] %score.values) >= 2;
say "\nWinner: " ~ %score.invert.Hash{ %score.values.max };
say "Final Score: " ~ %score{@sounds}.join(' to ');
ping 1 pong 2 ping 3 pong 4 Miss by ping. Score: 0 to 1
ping 1 pong 2 ping 3 pong 4 ping 5 Miss by pong. Score: 1 to 1
ping 1 Miss by pong. Score: 2 to 1
ping 1 pong 2 Miss by ping. Score: 2 to 2
ping 1 pong 2 ping 3 pong 4 ping 5 pong 6 ping 7 pong 8 ping 9 Miss by pong. Score: 3 to 2
--Now serving: pong--
pong 1 ping 2 pong 3 ping 4 Miss by pong. Score: 4 to 2
pong 1 ping 2 pong 3 ping 4 pong 5 Miss by ping. Score: 4 to 3
pong 1 Miss by ping. Score: 4 to 4
pong 1 Miss by ping. Score: 4 to 5
pong 1 ping 2 pong 3 ping 4 pong 5 Miss by ping. Score: 4 to 6
--Now serving: ping--
ping 1 pong 2 Miss by ping. Score: 4 to 7
ping 1 pong 2 Miss by ping. Score: 4 to 8
ping 1 pong 2 ping 3 pong 4 ping 5 Miss by pong. Score: 5 to 8
ping 1 Miss by pong. Score: 6 to 8
ping 1 pong 2 ping 3 Miss by pong. Score: 7 to 8
--Now serving: pong--
Miss by pong. Score: 8 to 8
Miss by pong. Score: 9 to 8
pong 1 ping 2 pong 3 Miss by ping. Score: 9 to 9
pong 1 ping 2 pong 3 ping 4 pong 5 ping 6 pong 7 ping 8 Miss by pong. Score: 10 to 9
pong 1 ping 2 Miss by pong. Score: 11 to 9
--Now serving: ping--
Winner: ping
Final Score: 11 to 9
一些评论和注意事项:
- $.sound 处理字符串化。
- 如果 promise 是 Kept 的,循环退出,所以玩家错过了。
- » 与 » 相同 - 这就像 map。
- 在@sounds 中添加另一个条目会设置一个消息传递循环。
结论和进一步的想法
- Channel 是在线程之间传递消息的好方法。
- Channels 和 Promises 可以是对象的属性。
- 这对于消息传递的封装很方便。