如何共享包含文件句柄的对象?

时间:2011-11-20 07:49:26

标签: multithreading perl

Perl线程不支持共享文件句柄。必须共享共享数据结构的所有元素。如果需要共享包含文件句柄的对象,则会出现问题。

{
    package Foo;
    use Mouse;

    has fh =>
      is      => 'rw',
      default => sub { \*STDOUT };
}

use threads;
use threads::shared;
my $obj = Foo->new;
$obj = shared_clone($obj);           # error: "Unsupported ref type: GLOB"
print {$obj->fh} "Hello, world!\n";

文件句柄是否“共享”无关紧要,它仅用于输出。也许有一个技巧,文件句柄存储在共享对象之外?

此对象实际上包含在另一个共享对象中,依此类推。具有讽刺意味的是,有问题的对象从不使用线程本身,但如果用户使用线程,则必须在整个过程中保持协调。

有问题的真实代码can be seen here:这些对象用于配置格式化输出的位置。一个对象是必要的,因为output does not always go to a filehandle

5 个答案:

答案 0 :(得分:6)

我目前无权访问线程Perl,因此无法保证这将有效。

但有点简单的方法是使用抽象级别并将键/索引存储到对象中的全局文件句柄哈希/数组中,类似于以下内容:

my @filehandles = (); # Stores all the filehandles         ### CHANGED

my $stdout; # Store the index into @filehandles, NOT filehandle.
            # Should really be renamed "$stdout_id" instead.

sub stdout {
    my $self = shift;

    return $stdout if defined $stdout;

    $stdout = scalar(@filehandles);                         ### CHANGED
    my $stdout_fh = $self->dup_filehandle(\*STDOUT);        ### CHANGED
    push @filehandles, $stdout_fh;                          ### CHANGED

    $self->autoflush($stdout_fh);                           ### CHANGED
    $self->autoflush(\*STDOUT);

    return $stdout;
}

sub safe_print {
    my $self = shift;
    my $fh_id = shift;                                       ### CHANGED
    my $fh = $filehandles[$fh_id];                           ### CHANGED

    local( $\, $, ) = ( undef, '' );
    print $fh @_; 
}

我有一种强烈的感觉,你需要以某种方式对ID列表进行线程安全,因此可能需要一个共享索引计数器而不是$stdout = scalar(@filehandles);

答案 1 :(得分:5)

作为我对全局数组的另一个答案的替代方法,这是Perlmonks的另一种方法:

http://perlmonks.org/?node_id=395513

它通过实际存储文件句柄的fileno(文件描述符)来工作。以下是基于BrowserUk发布的示例代码:

my $stdout; # Store the fileno, NOT filehandle.
            # Should really be renamed "$stdout_fileno" instead.

sub stdout {
    my $self = shift;

    return $stdout if defined $stdout;

    my $stdout_fh = $self->dup_filehandle(\*STDOUT);        ### CHANGED
    $stdout = fileno $stdout_fh;                            ### CHANGED

    $self->autoflush($stdout_fh);                           ### CHANGED
    $self->autoflush(\*STDOUT);

    return $stdout;
}

sub safe_print {
    my $self = shift;
    my $fh_id = shift;                                       ### CHANGED
    open(my $fh, ">>&=$fh_id")                                ### CHANGED
        || die "Error opening filehandle: $fh_id: $!\n";     ### CHANGED

    local( $\, $, ) = ( undef, '' );
    print $fh @_; 
}

CAVEAT - 截至2004年,这有一个错误,你无法从> 1线程的共享文件句柄中读取。我猜是写得不错。有关如何在共享文件句柄(来自同一个Monk)上执行同步写入的更多细节:http://www.perlmonks.org/?node_id=807540

答案 2 :(得分:3)

我刚想到有两种可能的解决方案:

  1. 将文件句柄放在Streamer对象之外。
  2. 将Streamer对象放在Formatter之外。
  3. @ DVK的建议都是关于做1。

    但是2在某种程度上比1更简单。格式化程序可以为Streamer对象保存一个标识符,而不是保持Streamer对象本身。如果Streamer是由内到外实现的,那自然会发生!

    不幸的是,参考地址在线程之间变化,甚至是共享线程。这可以用Hash::Util::FieldHash来解决,但那是5.10,我必须支持5.8。可以使用CLONE将某些内容组合在一起。

答案 3 :(得分:1)

这是我结束的......

package ThreadSafeFilehandle;

use Mouse;
use Mouse::Util::TypeConstraints;

my %Filehandle_Storage;    # unshared storage of filehandles
my $Storage_Counter = 1;   # a counter to use as a key

# This "type" exists to intercept incoming filehandles.
# The filehandle goes into %Filehandle_Storage and the
# object gets the key.
subtype 'FilehandleKey' =>
  as 'Int';
coerce 'FilehandleKey' =>
  from 'Defined',
  via {
      my $key = $Storage_Counter++;
      $Filehandle_Storage{$key} = $_;
      return $key;
  };

has thread_safe_fh =>
  is            => 'rw',
  isa           => 'FilehandleKey',
  coerce        => 1,
;

# This converts the stored key back into a filehandle upon getting.
around thread_safe_fh => sub {
    my $orig = shift;
    my $self = shift;

    if( @_ ) {                  # setting
        return $self->$orig(@_);
    }
    else {                      # getting
        my $key = $self->$orig;
        return $Filehandle_Storage{$key};
    }
};

1;

使用类型强制确保即使在对象构造函数中也会发生从文件句柄到键的转换。

它有效,但它有缺陷:

每个对象都冗余地存储其文件句柄。如果一堆对象都存储相同的文件句柄,那么它们可能只存储一次。诀窍是如何识别相同的文件句柄。 fileno或refaddr是选项。

删除对象后,不会从%Filehandle_Storage中删除文件句柄。我最初使用DESTROY方法来执行此操作,但由于对象克隆习惯用语是$clone = shared_clone($obj),因此一旦$ obj超出范围,$ clone的文件句柄就会被删除。

不会共享儿童中发生的变化。

这些都可以用于我的目的,每个过程只会创建少数这些对象。

答案 4 :(得分:0)

然后,如果一个人对其控制剂没有过敏反应,可以使用https://metacpan.org/module/Coro