Introducción a Coro

p2@nereida:~/src/perl/Event/coro$ cat -n synopsis.pl
     1  #!/usr/bin/perl
     2  use strict;
     3  use warnings;
     4  use Coro;
     5
     6  async {
     7     # some asynchronous thread of execution
     8     print "2\n";
     9     cede; # yield back to main
    10     print "4\n";
    11  };
    12  print "1\n";
    13  cede; # yield to coroutine
    14  print "3\n";
    15  cede; # and again

Cuando una rutina cede el control a otra su estado permanece activo: Se guarda no sólo su contador de programa sino también sus datos. En particular, en el módulo Coro una corutina guarda no sólo el control sino las variables léxicas, las variables @_, $_, $@, $/ y una pila de trabajo (implantada en C).

Al ejecutar el anterior programa se obtiene:

pp2@nereida:~/src/perl/Event/coro$ synopsis.pl
1
2
3
4

Asi pués mientras que el clásico modelo de llamada a subrutina se implanta por medio de una pila lineal, las corutinas puede requerir la asignación de pilas adicionales.

Una llamada:

                    async { ... } [ @args ]
Crea una corutina asíncrona y retorna un objeto corutina (clase Coro ). De hecho el objeto es un hash vacío. En el ejemplo no se usa el valor retornado.

Excepciones y Salida de una Corutina

Si una corutina ejecuta exit o lanza una excepción el programa termina.

p2@nereida:~/src/perl/coro$ cat -n exit.pl
     1  #!/usr/bin/perl
     2  use strict;
     3  use warnings;
     4  use Coro;
     5
     6  async {
     7     print "2\n";
     8     cede; # yield back to main
     9     exit;
    10  };
    11  print "1\n";
    12  cede; # yield to coroutine
    13  print "3\n";
    14  cede; # and again
pp2@nereida:~/src/perl/coro$ exit.pl
1
2
3

Localización de Variables

Las siguientes variables globales se inicializan al comienzo de una corutina:

Variable Valor Inicial
@_ Los argumentos pasados a la corutina
$_ undef
$@ undef
$1, $2, etc. undef

El ejemplo muestra como las variables mencionadas son localizadas automáticamente:

pp2@nereida:~/src/perl/coro$ cat -n globalvariables.pl
     1  #!/usr/bin/perl
     2  use strict;
     3  use warnings;
     4  use Coro;
     5
     6  $_ = "main";
     7  $/ = "***";
     8  async {
     9     # some asynchronous thread of execution
    10     $_ = "coroutine";
    11     $/ = "---";
    12     print "\$_ = <$_> \$/ = $/\n";
    13     print "$_[0]\n";
    14     cede; # yield back to main
    15     print "$_[1]\n";
    16  } 2,4;
    17  print "1\n";
    18  cede; # yield to coroutine
    19  print "3\n";
    20  cede; # and again
    21  print "\$_ = <$_> \$/ = $/\n";
pp2@nereida:~/src/perl/coro$ globalvariables.pl
1
$_ = <coroutine> $/ = ---
2
3
4
$_ = <main> $/ = ***

Las variables main, current y idle

El ejemplo ilustra el funcionamiento de estas variables. La llamada a scheduler llama a la siguiente corutina pero no empuja la corutina actual en la cola.

pp2@nereida:~/src/perl/coro$ perl -wd nothingtodo.pl
main::(nothingtodo.pl:11):      };
  DB<1> l 1,14
1       #!/usr/bin/perl
2:      use strict;
3:      use warnings;
4:      use Coro;
5
6       async {
7          # some asynchronous thread of execution
8:         print "2\n";
9:         cede; # yield back to main
10:        print "4\n";
11==>   };
12:     print "1\n";
13:     schedule;    # finished!
14:     print "3\n"; # never executed
  DB<2> c 8
1
main::CODE(0x814f840)(nothingtodo.pl:8):
8:         print "2\n";
  DB<3> p $Coro::main
Coro=HASH(0x83eca58)
  DB<4> p $Coro::current
Coro=HASH(0x846df04)
  DB<5> l $Coro::idle
Interpreted as: $Coro::idle Coro::__ANON__[/usr/local/lib/perl/5.8.8/Coro.pm:153]
Switching to file '/usr/local/lib/perl/5.8.8/Coro.pm'.
150     $idle = sub {
151:       require Carp;
152:       Carp::croak ("FATAL: deadlock detected");
153:    };
  DB<6> $Coro::idle = sub { die "Nothing to do!!" }

  DB<7> c
2
4
Nothing to do!! at (eval 11)[/usr/share/perl/5.8/perl5db.pl:628] line 2.

Las Subrutinas schedule y ready

La subrutina schedule llama al planificador. La corutina actual no será guardada en la cola de corutinas 'listas' como hace cede . Así pues una corutina que llama a schedule no volverá a actuar a menos que algun evento la introduzca de nuevo en la cola de listos (usando la función ready ).

pp2@nereida:~/src/perl/coro$ cat -n schedule.pl
 1  #!/usr/bin/perl
 2  use warnings;
 3  use strict;
 4
 5  use Coro;
 6  use Coro::Event;
 7
 8  async {
 9     print "2\n";
10     cede;
11     print "4\n";
12  };
13
14  sub timer : Coro {
15    my $w = Coro::Event->timer (after => 1);
16    my $e = $w->next; # get the event
17    print "Got: ".$e->got." Hits: ".$e->hits."\n";
18    $Coro::main->ready; # Put $main coroutine in the queue
19  }
20
21  print "1\n";
22  schedule;
23  print "3\n";

La corutina timer crea un vigilante en la clase Coro::Event::timer mediante la llamada:

    my $w = Coro::Event->timer (after => 1, repeat => 0);
Caundo este vigilante detecta el evento la ejecucíón de la corutina/callback vuelve a introducir la corutina $Coro::main en la cola de preparados dandole oportunidad de acabar.

pp2@nereida:~/src/perl/coro$ schedule.pl
1
2
4
Got: 0 Hits: 1
3

A diferencia de Event aquí no se especifica callback: El callback es la propia corutina.

El Atributo Coro

Como hemos visto es posible añadir el atributo Coro a una subrutina, lo que la convierte en corutina.

pp2@nereida:~/src/perl/coro$ cat -n attributes2.pl
 1  #!/usr/bin/perl
 2  use strict;
 3  use warnings;
 4
 5  use Coro;
 6
 7  my $p = 2;
 8
 9  sub p1 : Coro {
10     for (0..9) {
11        print "p1: $_\n";
12        cede;
13     }
14     $p--;
15  }
16
17  sub p2 : Coro {
18     for (10..23) {
19        print "p2: $_\n";
20        cede;
21     }
22     $p--;
23  }
24
25  eval {
26     cede while $p;
27  };

Argumentos y Retorno: terminate y cancel

Es posible pasar argumentos a la subrutina

                    async { ... } [ @args ]

y es posible retornar valores usando terminate o cancel :

terminate [arg ... ]
$coroutine->cancel( arg ... )
Los valores retornados pueden ser recuperados con join .

Sin embargo no es posible pasar parámetros a una corutina ni retornar valores cuando se cede el control a una corutina. De hecho cuando múltiples corutinas están activas es difícil sabe cuál tomará el control.

pp2@nereida:~/src/perl/coro$ cat -n isready.pl
 1  #!/usr/bin/perl
 2  use warnings;
 3  use strict;
 4  use Coro;
 5
 6  my $n = 3;
 7  my $end = 1;
 8  my $p = Coro->new( sub {
 9    my $m = shift;
10
11    for (0..$m) {
12      print "p: $_\n";
13      cede;
14    }
15    $end = 0;
16    terminate map { $_*$_ } 1..$m;
17  }, $n
18  );
19
20  $p->ready;
21  do {
22    cede;
23    print "H\n";
24  } while ($end);
25  my @r = $p->join;
26  print "This is the end:(@r)\n";

Al ejecutar este programa obtenemos la salida:

pp2@nereida:~/src/perl/coro$ isready.pl
p: 0
H
p: 1
H
p: 2
H
p: 3
H
H
This is the end:(1 4 9)

El Método on_destroy

El método on_destroy registra un callback que será llamado cuando la corutina sea destruida, pero antes de que ocurra el join. El ejemplo anterior puede reescribirse como sigue:

pp2@nereida:~/src/perl/coro$ cat -n ondestroy.pl
 1  #!/usr/bin/perl
 2  use warnings;
 3  use strict;
 4  use Coro;
 5
 6  my $n = 3;
 7  my $continue = 1;
 8
 9  my $p = Coro->new( sub {
10    my $m = shift;
11
12    for (0..$m) {
13      print "p: $_\n";
14      cede;
15    }
16    terminate map { $_*$_ } 1..$m;
17  }, $n
18  );
19
20  $p->on_destroy( sub { $continue = 0 } );
21  $p->ready;
22  do {
23    cede;
24    print "H\n";
25  } while ($continue);
26  my @r = $p->join;
27  print "This is the end:(@r)\n";
Cuando se ejecuta se obtiene la siguiente salida:

pp2@nereida:~/src/perl/coro$ ondestroy.pl
p: 0
H
p: 1
H
p: 2
H
p: 3
H
H
This is the end:(1 4 9)

Prioridades

El método prio establece la prioridad de una corutina. Las corutinas con prioridad un número mayor se ejecutan con mas frecuencia que las que tienen un número más pequeño.

Nombre Valor
PRIO_MAX 3
PRIO_HIGH 1
PRIO_NORMAL 0
PRIO_LOW -1
PRIO_IDLE -3
PRIO_MIN -4

La corutina $Coro::idle tiene menos prioridad que ninguna otra.

Los cambios en la prioridad de la corutina actual tiene lugar inmediatamente. Sin embargo, los cambios efectuados en corutinas que esperan en la cola de preparados solo tiene lugar después de su siguiente planificación.

pp2@nereida:~/src/perl/coro$ cat -n priorities.pl
 1  #!/usr/bin/perl
 2  use strict;
 3  use warnings;
 4  use Coro qw{:prio cede async};
 5
 6  my $prio = shift || PRIO_NORMAL;
 7  my $c = async {
 8     print "2\n";
 9     cede; # yield back to main
10     print "4\n";
11  };
12
13  $c->prio($prio);
14
15  print "1\n";
16  cede; # yield to coroutine
17  print "3\n";
18  cede; # and again

Cuando se ejecuta el programa anterior se obtienen salidas como estas:

pp2@nereida:~/src/perl/coro$ priorities.pl 3 # PRIO_MAX
1
2
4
3
pp2@nereida:~/src/perl/coro$ priorities.pl 1 # PRIO_HIGH
1
2
4
3
pp2@nereida:~/src/perl/coro$ priorities.pl 0 # PRIO_NORMAL
1
2
3
4
pp2@nereida:~/src/perl/coro$ priorities.pl -1 # PRIO_LOW
1
2
3
pp2@nereida:~/src/perl/coro$ priorities.pl -3 # PRIO_IDLE
1
2
3
pp2@nereida:~/src/perl/coro$ priorities.pl -4 # PRIO_MIN
1
2
3
pp2@nereida:~/src/perl/coro$ priorities.pl -5 # No existe
1
2
3
pp2@nereida:~/src/perl/coro$ priorities.pl -300 # No se obtienen errores
1
2
3
El método nice
$newprio = $coroutine->nice($change)
puede ser utilizado para cambiar el valor de la prioridad de $coroutine. En este caso $change es un desplazamiento que se resta a la prioridad actual.

Mientras que cede cede el control a corutinas con prioridad igual o superior, la subrutina Coro::cede_notself cede el control a cualquier corutina, independientemente de cual sea su prioridad.

pp2@nereida:~/src/perl/coro$ cat -n prioidle.pl
     1  #!/usr/bin/perl
     2  use strict;
     3  use warnings;
     4  use Coro qw{:prio async};
     5
     6  my $prio = shift || PRIO_NORMAL;
     7  my $c = async {
     8     print "2\n";
     9     Coro::cede_notself; # yield back to main
    10     print "4\n";
    11  };
    12
    13  $c->prio($prio);
    14
    15  print "1\n";
    16  Coro::cede_notself; # yield to coroutine
    17  print "3\n";
    18  Coro::cede_notself; # and again
Al ejecutarse produce:
pp2@nereida:~/src/perl/coro$ prioidle.pl -1
1
2
3
4
pp2@nereida:~/src/perl/coro$ prioidle.pl -2
1
2
3
4
pp2@nereida:~/src/perl/coro$ prioidle.pl -3
1
2
3
4
pp2@nereida:~/src/perl/coro$ prioidle.pl -4
1
2
3
4



Subsecciones
Casiano Rodríguez León
Licencia de Creative Commons
Programación Distribuida y Mejora del Rendimiento
por Casiano Rodríguez León is licensed under a Creative Commons Reconocimiento 3.0 Unported License.

Permissions beyond the scope of this license may be available at http://campusvirtual.ull.es/ocw/course/view.php?id=44.
2012-06-19