2012-01-28 12 views
5

Ho appena introdotto i thread in un programma Perl, in cui uno dei suoi moduli utilizzava Memoize. Viene visualizzato questo messaggio di errore:Errore nell'utilizzo di ithreads con Memoize

Thread 1 terminato in modo anomalo: funzione anonima chiamata nel contesto scalare proibito; faulting

L'errore si verifica se ho entrambi i thread e Memoize, ma scomparirà se togli uno di questi elementi. Ma il problema non è perché Memoize non è thread-safe - nel mio codice, tutta la memoizzazione avviene all'interno della stessa discussione.

Si tratta di un errore di Memoize? C'è un modo per aggirare questo? Altrimenti mi sbarazzerò di Memoize.

Ecco alcuni esempi di codice per isolare il 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

Quale versione di perl stai utilizzando? (Chiedo a causa di [questo bug] (https://rt.perl.org/rt3/Public/Bug/Display.html?id=79996).) – Mat

+0

Sto usando Strawberry Perl 5.12.3 con Memoize 1.02. Non potrei riprodurre quell'errore. – stevenl

risposta

4

Memoize memorizza le cache per ogni funzione memorizzata in un hash (invece di utilizzare una chiusura). Usa l'indirizzo della funzione come indice in quell'hash.

Il problema è che l'indirizzo della funzione cambia quando è clonato in una nuova discussione. (Aggiungi print(\&foo, "\n"); in invoke_foo.). È un bug in Memoize.

Soluzione alternativa: caricare il modulo memoised dall'interno dei thread. il seguente simula (gli aspetti rilevanti di) quello:

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; 

A proposito, ogni thread ha una propria cache. potrebbe anche essere considerato un bug.

+0

Ho appena visto questo [bug report] (https://rt.cpan.org/Public/Bug/Display.html?id=21707) di 5 anni fa (ancora non risolto) – stevenl

1

Memoize dovrebbe funzionare sotto le discussioni, anche se un po 'più lento:

"c'è qualche problema con il modo in cui goto & f funziona sotto threaded Perl, forse a causa dello scope lessicale di @_. Questo è un bug in Perl, e fino a quando non viene risolto, le funzioni memoizzate vedranno leggermente un altro chiamante() e si esibiranno un po 'più lentamente sul threaded perls rispetto ai perls non letti. "

2

Come indicato, Memoize non è thread aware. Se vuoi una memoizzazione per thread, la ristrutturazione di ikegami funzionerà bene. Se invece si desidera Memoizzazione globale, allora sostituendo Memoize con qualcosa di simile al seguente potrebbe funzionare:

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) 
     } 
    } 
} 

e di utilizzarlo:

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 
} 

che stampa:

 
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 funzione memoize_shared sopra è abbastanza complicato perché si occupa di liste propede e contesti scalari e sostituisce la subroutine nominata.A volte è più facile costruire solo il memoziation nella subroutine di destinazione:

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

Costruire il Memoizzazione nella subroutine fa renderlo un po 'più complicato, ma sarà più veloce rispetto all'utilizzo di una funzione wrapper come memoize. E ti dà il controllo esatto su come memoizzare la subroutine, comprese cose come l'uso di una cache threads::shared.

Problemi correlati