Ping Pang in Raku

Message passing between threads using channels

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 处理字符串化。
  • 如果 promiseKept 的,循环退出,所以玩家错过了。
  • » 与 » 相同 - 这就像 map
  • 在@sounds 中添加另一个条目会设置一个消息传递循环。

结论和进一步的想法

  • Channel 是在线程之间传递消息的好方法。
  • Channels 和 Promises 可以是对象的属性。
  • 这对于消息传递的封装很方便。

comments powered by Disqus