2012-01-28 56 views
5

我剛剛介紹了一個Perl程序的線程,其中一個模塊使用Memoize。 我收到此錯誤消息:使用ithreads與Memoize時出錯

線程1異常終止:在禁止標量上下文中調用的匿名函數;斷裂

的錯誤發生,如果我有兩個線程和memoize的,但如果我拿走這些元素之一就會消失。但問題不是因爲Memoize不是線程安全的 - 在我的代碼中,所有的memoization都發生在同一個線程中。

這是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? (因爲[這個bug]的詢問(https://rt.perl.org/rt3/Public/Bug/Display.html?id=79996)。) – Mat 2012-01-28 15:08:48

+0

我使用Perl的草莓與5.12.3 1.02 memoize的。我無法重現該錯誤。 – stevenl 2012-01-28 15:35:38

回答

4

記憶將每個記憶函數的緩存存儲在一個散列中(而不是使用閉包)。它使用函數的地址作爲該散列的索引。

問題是,當函數的地址被克隆到一個新的線程中時,它的地址會發生變化。 (在invoke_foo中添加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

我剛看到這個[錯誤報告](https://rt.cpan.org/Public/Bug/Display.html?id=21707)從5年前(仍未解決) – stevenl 2012-01-29 03:48:23

1

memoize的應在線程工作,雖然有點慢:

「有一些問題的方式轉到&˚F工作下這可能是因爲@_的詞法範圍,這是Perl中的一個bug,在解析之前,memoized函數將會看到一個稍微不同的調用者(),並且在線程上執行的速度會稍微慢一點點 perls比無螺紋perls。「

2

如上所述,Memoize不是線程感知的。如果你想每個線程memoization,ikegami的重組將很好。相反,如果你想全球記憶化,然後用類似下面的更換Memoize可以工作:

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

,並使用它:

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緩存等。