2012-01-28 2 views
5

Perlプログラムにスレッドを導入しました。そのモジュールの1つがMemoizeを使用していました。 私は、このエラーメッセージが出てい:Memoizeでithreadを使用するとエラーが発生する

スレッド1が異常終了:禁断のスカラコンテキストで呼び出される匿名関数を、エラー

これらの要素の1つを取り除くと、スレッドとメモ帳の両方がある場合にエラーが発生しますが、消えてしまいます。しかし、問題はMemoizeがスレッドセーフではないためではありません。私のコードでは、すべてのメモは同じスレッド内で発生します。

これはMemoizeのバグですか?私はこれを回避できる方法はありますか?さもなければ私はMemoizeを取り除くつもりです。

は、ここで問題を特定するためにいくつかのサンプルコードです:

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

実行中のPerlのバージョンは? ([このバグ](https://rt.perl.org/rt3/Public/Bug/Display.html?id=79996)のため)。 – Mat

+0

私はMemoize 1.02でStrawberry Perl 5.12.3を使用しています。私はそのバグを再現できませんでした。 – stevenl

答えて

4

memoizeは、memoized関数ごとに1つのハッシュ(クロージャを使用する代わりに)にキャッシュを格納します。関数のアドレスをそのハッシュのインデックスとして使用します。

問題は、関数のアドレスが新しいスレッドにクローンされるときに変更されることです。 (print(\&foo, "\n");をに加える)。これはMemoizeのバグです。

回避方法:memoisedモジュールをスレッド内からロードします。

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; 

ちなみに、各スレッドは独自のキャッシュを持っています(関連する側面)。それはまたバグとみなされる可能性があります。

+0

5年前からこの[バグレポート](https://rt.cpan.org/Public/Bug/Display.html?id=21707)を見ました(未解決です) – stevenl

1

Memoizeは少し遅くなるとはいえ、スレッドの下で動作するはずです:

「後藤& fは下に動作する方法といくつかの問題がありますこれはおそらく Perlのバグであり、解決されるまでmemoized関数はわずかに の呼び出し元()を参照し、スレッドでは少し遅くなります perlsよりもスレッド化されていないperl "

2

前述のように、Memoizeはスレッドを認識しません。スレッドごとのメモを作成したい場合は、ikegamiのリストラがうまくいきます。

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

をし、それを使用する:あなたがグローバルメモ化を望む代わり場合は、次のようなものでMemoizeを交換すると仕事ができる

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 
} 

印刷した:

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

memoize_shared機能をリストとスカラコンテキストの宣言と名前付きサブルーチンの置き換えを扱うため、上記はかなり複雑です。時々ちょうどターゲットサブルーチンにmemoziationを構築する方が簡単です:

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

サブルーチンにメモ化を構築することは、それは少し複雑にするんが、それはmemoizeのようなラッパー関数を使用するよりも速くなります。また、threads::sharedキャッシュの使用など、サブルーチンをメモする方法を正確に制御できます。

関連する問題