2012-01-28 9 views
5

Acabo de introducir hilos a un programa Perl, donde uno de sus módulos estaba usando Memoize. Recibo este mensaje de error:Error al utilizar ithreads con Memoize

Subproceso 1 terminado anormalmente: función anónima llamada en contexto escalar prohibido; fallando

El error ocurre si tengo ambos hilos y Memoize, pero desaparecerá si quito uno de estos elementos. Pero el problema no es porque Memoize no es seguro para subprocesos: en mi código, todas las memorias se realizan dentro del mismo hilo.

¿Este es un error con Memoize? ¿Hay alguna manera de solucionar este problema? De lo contrario, me desharé de Memoize.

He aquí algunos ejemplos de código para aislar el problema:

use strict; 
use warnings; 
use threads; 
use Thread::Semaphore; 
use Memoize; 

my $semaphore = Thread::Semaphore->new; 

memoize('foo'); 
sub foo { 
    return shift; 
} 

sub invoke_foo { 
    $semaphore->down; # ensure memoization is thread-safe 
    my $result = foo(@_); 
    $semaphore->up; 

    return $result; 
} 

my @threads; 
foreach (1 .. 5) { 
    my $t = threads->create(sub { invoke_foo($_) }); 
    push @threads, $t; 
} 
$_->join foreach @threads; 
+2

¿Qué versión de perl está ejecutando? (Preguntando por [este error] (https://rt.perl.org/rt3/Public/Bug/Display.html?id=79996)) – Mat

+0

Estoy usando Strawberry Perl 5.12.3 con Memoize 1.02. No pude reproducir ese error. – stevenl

Respuesta

4

Memoize almacena las cachés para cada función memorizada en un hash (en lugar de utilizar un cierre). Utiliza la dirección de la función como el índice en ese hash.

El problema es que la dirección de la función cambia cuando se clona en un nuevo hilo. (Agregue print(\&foo, "\n"); en invoke_foo). Es un error en Memoize.

Solución alternativa: cargue el módulo memorizado desde dentro de los hilos. simula los siguientes (los aspectos relevantes de) que:

use strict; 
use warnings; 
use threads; 
use Memoize; 

sub foo { 
    return shift; 
} 

sub invoke_foo { 
    return foo(@_); 
} 

my @threads; 
foreach (1 .. 5) { 
    my $t = threads->create(sub { 
     memoize('foo'); 
     invoke_foo($_); 
    }); 
    push @threads, $t; 
} 
$_->join foreach @threads; 

Por cierto, cada hilo tiene su propia caché. eso también podría ser considerado un error.

+0

Acabo de ver este [informe de error] (https://rt.cpan.org/Public/Bug/Display.html?id=21707) de hace 5 años (aún sin resolver) – stevenl

1

memoize debe trabajar bajo las discusiones, aunque un poco más lento:

"hay algún problema con la forma en Goto & f trabaja bajo Perl enhebrado, tal vez debido al alcance léxico de @_. Este es un error en Perl, y hasta que se resuelva, las funciones memoradas verán un llamador ligeramente diferente () y se ejecutarán un poco más lentamente en elroscadoperls que perls sin rosca ".

2

Como se señaló, Memoize no es consciente de los subprocesos. Si quiere por la memoria del hilo, la reestructuración de ikegami funcionará bien. Si por el contrario desea memoization mundial, volviendo a poner Memoize con algo como la siguiente podría funcionar:

use strict; 
use warnings; 
use 5.010; 
use threads; 
use threads::shared; 

sub memoize_shared { 
    my $name = shift; 
    my $glob = do { 
     no strict 'refs'; 
     \*{(caller)."::$name"} 
    }; 
    my $code = \&$glob; 
    my $sep = $;; 
    my (%scalar, %list) :shared; 

    no warnings 'redefine'; 
    *$glob = sub { 
     my $arg = join $sep => @_; 
     if (wantarray) { 
      @{$list{$arg} ||= sub {\@_}->(&$code)} 
     } 
     else { 
      exists $scalar{$arg} 
       ? $scalar{$arg} 
       :($scalar{$arg} = &$code) 
     } 
    } 
} 

y utilizarlo:

sub foo { 
    my $x = shift; 
    say "foo called with '$x'"; 
    "foo($x)" 
} 

memoize_shared 'foo'; 

for my $t (1 .. 4) { 
    threads->create(sub { 
     my $x = foo 'bar'; 
     say "thread $t got $x" 
    })->join 
} 

que imprime:

 
foo called with 'bar' 
thread 1 got foo(bar) 
thread 2 got foo(bar) 
thread 3 got foo(bar) 
thread 4 got foo(bar) 

La función memoize_shared arriba es bastante complicado porque trata con la propegación de la lista y los contextos escalares, así como la sustitución de la subrutina nombrada.A veces es más fácil simplemente construir el memoziation a la subrutina de destino:

{my %cache :shared; 
sub foo { 
    my $x = shift; 
    if (exists $cache{$x}) {$cache{$x}} 
    else { 
     say "foo called with '$x'"; 
     $cache{$x} = "foo($x)" 
    } 
}} 

Construyendo el memoization a la subrutina hace que sea un poco más complicado, pero será más rápido que utilizando una función de contenedor como memoize. Y le da control exacto sobre cómo memorizar la subrutina, incluyendo cosas como usar un caché threads::shared.

Cuestiones relacionadas