Ошибка при использовании ithreads с Memoize - PullRequest
5 голосов
/ 28 января 2012

Я только что представил потоки в программе на Perl, где один из ее модулей использовал Memoize .Я получаю это сообщение об ошибке:

Поток 1 прерван ненормально: анонимная функция вызвана в запрещенном скалярном контексте;faulting

Ошибка возникает, если у меня есть оба потока и Memoize, но исчезнет, ​​если я уберу один из этих элементов.Но проблема не в том, что 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;

Ответы [ 3 ]

4 голосов
/ 28 января 2012

Memoize хранит кэши для каждой запомненной функции в одном хеше (вместо использования замыкания). Он использует адрес функции в качестве индекса для этого хеша.

Проблема в том, что адрес функции изменяется, когда она клонируется в новый поток. (Добавьте print(\&foo, "\n"); в invoke_foo.). Это ошибка в Memoize.

Обходной путь: Загрузите запомнившийся модуль из потоков. следующее моделирует (соответствующие аспекты), что:

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;

Кстати, каждый поток имеет свой кеш. это также можно считать ошибкой.

2 голосов
/ 29 января 2012

Как отмечалось, 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 кэша.

1 голос
/ 28 января 2012

Memoize должен работать под потоками, хотя и немного медленнее:

"Существует некоторая проблема с тем, как goto & f работает в многопоточном Perl, возможно, из-за лексической области видимости @_. Это ошибка в Perl, и до тех пор, пока он не будет решен, запомненные функции будут видеть другой вызывающий () и будет работать немного медленнее на потоке Перлз, чем нетрезвый Перлз. "

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...