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

Proper Way to Provide Variable to Objects below the present calling subroutine?

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

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

use SAFARI::View;
my $view = SAFARI::View->new;
$view->output;

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

# Display user name:
$output .= 'Hello, ' . $main::author->{'fullName'} . '! Welcome!';

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

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

我考虑过创建一个散列引用,在每个线程中包含所有不同的位,例如$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 });

这不是太乏味,但似乎效率低下;如果 的“环境”只是 该线程可以包含其中的对象可以访问的内容,那将是更可取的。据我所知,在线程 运行 的子例程中声明 our $common 会执行此操作,并且在该子例程中创建的任何对象都可以访问该变量。这种做法有什么坏处吗?

似乎将这些项目以某种名称space 命名会更简洁,但如果我指的是 $SAFARI::common,则该名称 space 会达到就像 main 一样跨线程。拥有 $SAFARI::common 但随后在每个线程中声明它的 local 变体是否合理?

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

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

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

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

因此您可以在 main:: 中创建您的初始 $common 结构,然后创建线程,每个线程都将获得自己的副本。然后线程可以创建自己的 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;
}

这会打印

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

如果相反,您需要共享数据结构,请参阅 ,例如。


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

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

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

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

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

派生 classes 没有神奇的方法从父级获取 数据,而且您不需要基数 class 原则上必须知道其派生的 classes。很好地实例化所有 subclasses 并没有错,就像问题本身一样。

init_common只需要定义在SAFARI,父class

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;

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

派生子classes 无需提及任何此属性或 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;

继承已经到位了,否则组合或角色将是不错的选择。即便如此,还是要考虑在这里使用一个角色;例如,参见

如果'common'数据可以传递给构造函数

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

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

# ... set/customize $common

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