并发性,一次对多个供应做出反应

Concurrency, react-ing to more than one supply at a time

请考虑以下代码。为什么它的输出是 "BABABA" 而不是 "AAABAA" / "AABAAAB"?两个电源 运行 不应该并联并且当它们中的任何一个发生事件时立即触发吗?

my $i = 0; 
my $supply1 = supply { loop { await Promise.in(3); done if $i++> 5; emit("B"); } };
my $supply2 = supply { loop { await Promise.in(1); done if $i++> 5; emit("A"); } };

react 
{ 
    #whenever Supply.merge($supply1, $supply2) -> $x { $x.print }
    whenever $supply1 -> $x { $x.print };
    whenever $supply2 -> $x { $x.print };
}

Supplies are asynchronous, not concurrent. You will need to use channels 而不是供应同时喂养他们。

use v6;

my $i = 0;
my Channel $c .= new;
my $supply1 = start { for ^5 { await Promise.in(1); $c.send("B"); } };
my $supply2 = start { for ^5 { await Promise.in(0.5); $c.send("A"); } };

await $supply2;
await $supply1;
$c.close;

.say for $c.list;

在这种情况下,两个线程同时启动,而不是使用.emit,然后.send到通道。在您的示例中,它们在等待时实际上被阻塞了,因为它们都 运行ning 在同一个线程中。他们只在遵守诺言后才控制其他供应,因此他们 运行 显然 "in parallel" 和他们中较慢的人一样慢。

感谢这里的 jjmerelo,我设法让它工作了。渠道是正轨,但实际上你要消耗渠道供应。

use v6;

my Channel $c .= new;
my $supply1 = start { loop { await Promise.in(1); $c.send("B"); } };
my $supply2 = start { loop { await Promise.in(0.5); $c.send("A"); } };

react 
{ 
    whenever $c.Supply -> $x { $x.print };
}

$c.close;

附加问题:这个规模有多好?能不能给频道送上几千物资?

当我们订阅一个 supply 块时,该 supply 块的主体立即 运行 以设置订阅。其中没有引入并发性;如果我们想要它,我们需要提出要求。

最佳解决方案取决于示例与您正在做的事情的接近程度。如果它非常接近 - 并且你希望每个时间间隔 emit 值 - 那么解决方案是使用 Supply.interval 代替:

my $i = 0; 
my $supply1 = supply { whenever Supply.interval(3, 3) { done if $i++ > 5; emit("B"); } };
my $supply2 = supply { whenever Supply.interval(1, 1) { done if $i++> 5; emit("A"); } };

react { 
    whenever $supply1 -> $x { $x.print };
    whenever $supply2 -> $x { $x.print };
}

它只是设置订阅并退出设置,因此会提供您想要的输出,但是您在 $i.

上确实存在数据竞争

更一般的模式是只做 任何事情 让循环发生在设置步骤之外。例如,我们可以使用一个保留的 Promise 来只是 "thunk" 它:

my constant READY = Promise.kept;
my $i = 0;
my $supply1 = supply whenever READY {
    loop { await Promise.in(3); done if $i++> 5; emit("B"); }
}
my $supply2 = supply whenever READY {
    loop { await Promise.in(1); done if $i++> 5; emit("A"); }
}

react { 
    whenever $supply1 -> $x { $x.print };
    whenever $supply2 -> $x { $x.print };
}

这有帮助,因为 Promise 的结果将通过线程池调度程序传递到 supply 块,从而强制执行 whenever 的内容 - 包含循环 - 进入它自己的计划任务。

这不是特别漂亮,但是如果我们定义一个函数来做到这一点:

sub asynchronize(Supply $s) {
    supply whenever Promise.kept {
        whenever $s { .emit }
    }
}

那么原来的程序只需要加上两次调用就可以了:

my $i = 0;
my $supply1 = supply { loop { await Promise.in(3); done if $i++> 5; emit("B") } }
my $supply2 = supply { loop { await Promise.in(1); done if $i++> 5; emit("A") } }

react { 
    whenever asynchronize $supply1 -> $x { $x.print }
    whenever asynchronize $supply2 -> $x { $x.print }
}

让它按预期工作。可以说,像这样的东西应该作为内置提供。

也可以使用 Channel,正如其他解决方案所建议的那样,并且取决于手头可能合适的问题;这个问题有点太抽象了,我无法说出一个真正的问题。此解决方案保持在 Supply 范例内,并且在这个意义上更整洁。

好的,这是我的真实代码。它似乎有效,但我认为某处存在竞争条件。这是一些典型的(尽管很短)输出。

A monster hatched.
A monster hatched.
A hero was born.
The Monster is at 2,3
The Monster is at 3,2
The Player is at 0,0
The Monster (2) attacks the Player (3)
The Monster rolls 14
The Player rolls 4
The Monster inflicts 4 damage
The Player (3) attacks the Monster (2)
The Player rolls 11
The Monster rolls 8
The Player inflicts 45 damage
The Monster is dead
The Monster is at -3,-3
The Player is at 4,-3
The Monster (1) attacks the Player (3)
The Monster rolls 8
The Player rolls 5
The Monster inflicts 11 damage
The Player has 32 hitpoints left
The Monster is at -4,1
The Player is at -1,4
The Player (3) attacks the Monster (1)
The Player rolls 12
The Monster rolls 11
The Player inflicts 46 damage
The Monster is dead
Stopping
Game over. The Player has won

现在奇怪的是,有时,在大约 20% 的运行中,输出的最后一行是

Game over. The GameObject has won 

好像这个对象在它已经被部分解构的时候被抓住了?或者其他的东西?不管怎样,这是代码。

class GameObject
{
    has Int $.id;
    has Int $.x is rw;
    has Int $.y is rw;
    has $.game;
    has Int $.speed; #the higher the faster
    has Bool $.stopped is rw;

    multi method start( &action )
    {
        start {
            loop {
                &action();
                last if self.stopped;
                await Promise.in( 1 / self.speed );
            }
            $.game.remove-object( self );
        }
    }

    method speed {
        $!speed + 
            # 33% variation from the base speed in either direction
            ( -($!speed / 3).Int .. ($!speed / 3).Int ).pick
            ;
    }
}

role UnnecessaryViolence
{
    has $.damage;
    has $.hitpoints is rw;
    has $.offense;
    has $.defense;

    method attack ( GameObject $target )
    {
        say "The {self.WHAT.perl} ({self.id}) attacks the {$target.WHAT.perl} ({$target.id})";

        my $attacker = roll( $.offense, 1 .. 6 ).sum;
        say "The {self.WHAT.perl} rolls $attacker";

        my $defender = roll( $target.defense, 1 .. 6 ).sum;
        say "The {$target.WHAT.perl} rolls $defender";

        if $attacker > $defender 
        {
            my $damage = ( 1 .. $.damage ).pick;
            say "The {self.WHAT.perl} inflicts {$damage} damage";

            $target.hitpoints -= $damage ;
        }

        if $target.hitpoints < 0
        {
            say "The {$target.WHAT.perl} is dead";
            $target.stopped = True;
        }
        else
        {
            say "The {$target.WHAT.perl} has { $target.hitpoints } hitpoints left";
        }
    }
}

class Player is GameObject does UnnecessaryViolence
{
    has $.name;

    multi method start
    {
        say "A hero was born.";
        self.start({
            # say "The hero is moving";
            # keyboard logic here, in the meantime random movement
            $.game.channel.send( { object => self, x => (-1 .. 1).pick, y => (-1 .. 1).pick } );
        });
    }
}

class Monster is GameObject does UnnecessaryViolence
{
    has $.species;

    multi method start
    {
        say "A monster hatched.";
        self.start({
            # say "The monster {self.id} is moving";
            # AI logic here, in the meantime random movement
            $.game.channel.send( { object => self, x => (-1 .. 1).pick, y => (-1 .. 1).pick } );
        });
    }
}

class Game
{
    my $idc = 0;

    has GameObject @.objects is rw;
    has Channel $.channel = .new;

    method run{
        self.setup;
        self.mainloop;
    }

    method setup
    {
        self.add-object( Monster.new( :id(++$idc), :species("Troll"), :hitpoints(20), :damage(14), :offense(3), :speed(300), :defense(3), :x(3), :y(2), :game(self) ) );
        self.add-object( Monster.new( :id(++$idc), :species("Troll"), :hitpoints(10), :damage(16), :offense(3), :speed(400), :defense(3), :x(3), :y(2), :game(self) ) );
        self.add-object( Player.new( :id(++$idc), :name("Holli"), :hitpoints(50), :damage(60), :offense(3), :speed(200) :defense(2), :x(0), :y(0), :game(self) ) );
    }

    method add-object( GameObject $object )
    {
        @!objects.push( $object );
        $object.start;
    }

    method remove-object( GameObject $object )
    {
        @!objects = @!objects.grep({ !($_ === $object) });
    }

    method mainloop 
    { 
        react {
            whenever $.channel.Supply -> $event
            {
                self.stop-game
                    if self.all-objects-stopped;

                self.process-movement( $event );

                self.stop-objects
                  if self.game-is-over;

            };
            whenever Supply.interval(1) {
                self.render;
            }
        }

    }

    method process-movement( $event )
    {
        #say "The {$event<object>.WHAT.perl} moves.";
        given $event<object>
        {
            my $to-x = .x + $event<x>;
            my $to-y = .y + $event<y>;

            for @!objects -> $object
            {
                # we don't care abour ourselves
                next 
                    if $_ === $object;

                # see if anything is where we want to be
                if ( $to-x == $object.x && $to-y == $object.y )
                {
                    # can't move, blocked by friendly
                    return 
                        if $object.WHAT eqv .WHAT;

                    # we found a monster
                    .attack( $object );
                    last;
                }
            }

            # -5 -1 5 
            # we won the fight or the place is empty
            # so let's move
            .x = $to-x ==  5  ?? -4 !!
                 $to-x == -5  ?? 4  !!
                 $to-x;

            .y = $to-y ==  5  ?? -4 !!
                 $to-y == -5  ?? 4  !!
                 $to-y;

        }
    }

    method render
    {
        for @!objects -> $object {
            "The {$object.WHAT.perl} is at {$object.x},{$object.y}".say;
        }
    }

    method stop-objects
    {
        say "Stopping";
        for @!objects -> $object {
            $object.stopped = True;
        }
    }

    method stop-game {
        "Game over. The {@!objects[0].WHAT.perl} has won".say;
        $.channel.close;
        done;
    }

    method game-is-over {
        return (@!objects.map({.WHAT})).unique.elems == 1;
    }

    method all-objects-stopped {
        (@!objects.grep({!.stopped})).elems == 0;
    }



}

Game.new.run;