Как отмечалось, Memoize
не распознает потоки.Если вы хотите пометку за поток, реструктуризация 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
довольно сложна, потому что она имеет дело со встречным списком и скалярными контекстами, а также заменяет именованную подпрограмму.Иногда проще просто встроить памятку в целевую подпрограмму:
{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
кэша.