为当前调用子例程下方的对象提供变量的正确方法?

Tim*_*ler 3 oop perl multithreading

在我的代码库中,我有几个变量“存在于”main命名空间中,我使用的各种模块可以期望总是在 main 中找到(例如,$main::author是对用户散列的引用,$main::dbh是打开的数据库句柄,$main::loader是核心实用程序类的对象,并$main::FORM具有已处理的QUERY_STRING)。例如当程序启动时:

$main::author = &getAuthor;
$main::loader = SAFARI::Loader->new;

use SAFARI::View;
my $view = SAFARI::View->new;
$view->output;
Run Code Online (Sandbox Code Playgroud)

然后当我进入时SAFARI::View::output,我可以调用这些核心变量,例如:

# Display user name:
$output .= 'Hello, ' . $main::author->{'fullName'} . '! Welcome!';
Run Code Online (Sandbox Code Playgroud)

问题:当代码在线程环境中运行时,一个线程可能需要不同的$loader对象或$author与另一个线程不同的登录。更紧迫的是,当然,每个线程都有一个不同的数据库句柄。

我知道我可以在创建对象时传递核心信息,例如View通过添加它需要的参数。但这意味着目前只能引用main命名空间中这些项目的每种类型的对象都必须有一个冗长的参数列表。我试图想出最有效和“安全”的方法来解决这个问题。

我已经考虑创建散列引用具有每个线程内它的所有不同的位,例如$common,其具有$common->{'FORM'}$common->{'loader'}$common->{'author'},等等,然后通过那些作为单个参数到每个对象:

my $common = #Logic to set up this combination of bits for this particular thread.

my $view = SAFARI::View->new({ 'common' => $common });
my $article = SAFARI::Article->new({ 'common' => $common });
my $category = SAFARI::Category->new({ 'common' => $common });
Run Code Online (Sandbox Code Playgroud)

这不是太乏味,但似乎仍然效率低下;这将会是最好,如果的“环境”刚才那个线程可能包含的东西,在这范围内的对象可以访问。据我所知,our $common在线程运行的子例程中声明将执行此操作,并且在该子例程中创建的任何对象都可以访问该变量。这种方法有什么害处吗?

似乎将这些项目放在某种命名空间中会更清晰,但是如果我引用,比如说$SAFARI::common,该命名空间将像main那样跨越线程。将有$SAFARI::common但随后宣告local它在每个线程的变体是合理的?

我正在尝试做的事情是否有“最佳实践”?需要对代码进行一些重大的修改才能以一种或另一种方式解决这个问题,所以我真的很想让它“正确”。

zdi*_*dim 5

这是一个复杂的问题,有多个主要组成部分。

对于初学者来说,有一个问题是如何将初始数据结构传递给线程,以便它们可以使用它,但又不会被共享。

在 Perl 中,当创建线程时,现有数据被复制到每个线程。这就是为什么在程序中存在大量数据之前立即创建线程通常是一个好主意,以避免这些线程变得臃肿。默认情况下,在 Perl 中不共享复制的数据。

所以你可以在 中创建你的初始$common结构main::,然后创建线程,每个线程都会得到它自己的副本。然后线程可以创建自己的SAFARI::对象,并根据自己的意愿处理数据结构。一个简单的演示

use warnings;
use strict;
use feature 'say';
use Data::Dump qw(dd pp);

use threads;

my $ds = { val => 10, ra => [ 10..12 ] };

my @threads = map { async(\&proc_ds, $ds) } 1..3;

$_->join() for @threads;

say "main: ", pp $ds;

sub proc_ds {
    my ($ds) = @_; 

    # Modify data in the thread
    $ds->{val} += 10 * threads->tid;
    $_ += $ds->{val} for @{$ds->{ra}};

    say "Thread ", threads->tid, ": ", pp $ds;
}
Run Code Online (Sandbox Code Playgroud)

这打印

线程 1: { ra => [30, 31, 32], val => 20 }
线程 2: { ra => [40, 41, 42], val => 30 }
线程 3: { ra => [50, 51, 52], val => 40 }
主要:{ ra => [10, 11, 12], val => 10 }

例如,如果您需要共享数据结构,请参阅此页面


然后每个线程都需要使用一个类层次结构,其中多个子类应该使用在每个线程中修改的公共数据结构以相同的方式初始化。

在 Perl 的继承模型中,方法是从父类继承的,而不是数据。所以这里的问题是如何很好地将所有子类填充到相同的数据中。

有一些高级技术可以使过程更加优雅,但我建议简单地引入属性并在父类中定义方法,然后让所有子类使用它来初始化。这将非常清晰,并且与其他任何事情一样经济。喜欢

use SAFARI::View;
use SAFARI::Other;

# ... set/customize $common

my ($sview_obj, $sother_obj) = 
    map { $_->new->init_common($common) } 
        qw(SAFARI::View SAFARI::Other);

say "View object: $sview_obj";  # overload "" (quotes) for this, see below
Run Code Online (Sandbox Code Playgroud)

这将在每个线程中完成,$common首先根据需要对每个线程进行定制。

派生类没有一种神奇的方式从父类获取数据,并且原则上您不希望基类必须知道其派生类。很好地实例化所有子类并没有错,就像问题本身一样。

子类init_common只需要在SAFARI父类中定义

SAFARI.pm文件

package SAFARI;

use warnings;
use strict;

sub new {
    my ($class, @args) = @_;
    my $self = {
        common => {},  # introduce the attribute, for clarity
        # ... 
    };
    return bless $self, $class;
}

sub init_common {
    my ($self, $data) = @_; 
    $self->{common}->{dbh} = $data->{dbh};  # etc, populate common
    return $self;
}
...
1;
Run Code Online (Sandbox Code Playgroud)

如果它没有被设置,我们不需要在构造函数中指定属性,因为它将通过写入$self引用来创建init_common,但列出它有助于清晰。(当然,common属性也可以在构造‡ 时编写;而且,我们也不需要单独的方法。)

派生的子类不需要提及任何这些,属性或init_common方法,除非他们应该定制一些东西。

SAFARI/View.pm文件

package SAFARI::View;

use warnings;
use strict;
use feature 'say';
use Data::Dump qw(pp);

# Make sure @INC gets set as needed
use parent 'SAFARI';

use overload ( q("") => sub { return pp {%$_[0]} } );

# no need for init_common, nor for new (except to modify/override)

sub output { 
    my ($self, @args) = @_;
    say $self->{common}->{...};
    return $self;
};
...
1;
Run Code Online (Sandbox Code Playgroud)

继承已经到位,否则组合或角色将是不错的选择。即便如此,这里还是考虑使用角色;例如,请参阅这篇文章

如果“公共”数据可以传递给构造函数

sub new {
    my ($class, $attr) = @_;  # pass a hashref with attribute => value
    my $self = {
        common => {},  # introduce the attribute, for clarity
        # ... 
    };
    bless $self, $class;
    $self->init_common( $attr->{common} ) if exists $attr->{common};
    return $self;
}

sub init_common {
    my ($self, $data) = @_; 
    $self->{common}->{$_} = $data->{$_} for keys %$data;
    return $self;
}
Run Code Online (Sandbox Code Playgroud)

那么 'common' 也可以初始化为

# ... set/customize $common

my ($sview_obj, $sother_obj) = 
    map { $_->new( { common => $common } ) } 
        qw(SAFARI::View SAFARI::Other);
Run Code Online (Sandbox Code Playgroud)