内容

名称

threads::shared - Perl 扩展,用于在线程之间共享数据结构

版本

本文档描述了 threads::shared 版本 1.68

概要

use threads;
use threads::shared;

my $var :shared;
my %hsh :shared;
my @ary :shared;

my ($scalar, @array, %hash);
share($scalar);
share(@array);
share(%hash);

$var = $scalar_value;
$var = $shared_ref_value;
$var = shared_clone($non_shared_ref_value);
$var = shared_clone({'foo' => [qw/foo bar baz/]});

$hsh{'foo'} = $scalar_value;
$hsh{'bar'} = $shared_ref_value;
$hsh{'baz'} = shared_clone($non_shared_ref_value);
$hsh{'quz'} = shared_clone([1..3]);

$ary[0] = $scalar_value;
$ary[1] = $shared_ref_value;
$ary[2] = shared_clone($non_shared_ref_value);
$ary[3] = shared_clone([ {}, [] ]);

{ lock(%hash); ...  }

cond_wait($scalar);
cond_timedwait($scalar, time() + 30);
cond_broadcast(@array);
cond_signal(%hash);

my $lockvar :shared;
# condition var != lock var
cond_wait($var, $lockvar);
cond_timedwait($var, time()+30, $lockvar);

描述

默认情况下,变量对每个线程都是私有的,每个新创建的线程都会获得每个现有变量的私有副本。此模块允许您在不同的线程(以及 Win32 上的伪 fork)之间共享变量。它与 threads 模块一起使用。

此模块仅支持以下数据类型的共享:标量和标量引用、数组和数组引用以及哈希和哈希引用。

导出

此模块导出以下函数:shareshared_cloneis_sharedcond_waitcond_timedwaitcond_signalcond_broadcast

请注意,如果在 threads 尚未加载时导入此模块,则所有这些函数都将变为无操作。这使得编写可以在线程环境和非线程环境中工作的模块成为可能。

函数

share 变量

share 接受一个变量并将其标记为共享。

my ($scalar, @array, %hash);
share($scalar);
share(@array);
share(%hash);

share 将返回共享的右值,但始终作为引用。

变量也可以在编译时使用 :shared 属性标记为共享。

my ($var, %hash, @array) :shared;

共享变量只能存储标量、共享变量的引用或共享数据的引用(下一节讨论)。

my ($var, %hash, @array) :shared;
my $bork;

# Storing scalars
$var = 1;
$hash{'foo'} = 'bar';
$array[0] = 1.5;

# Storing shared refs
$var = \%hash;
$hash{'ary'} = \@array;
$array[1] = \$var;

# The following are errors:
#   $var = \$bork;                    # ref of non-shared variable
#   $hash{'bork'} = [];               # non-shared array ref
#   push(@array, { 'x' => 1 });       # non-shared hash ref
shared_clone 引用

shared_clone 接受一个引用,并返回其参数的共享版本,对任何非共享元素执行深层复制。参数中的任何共享元素按原样使用(即,它们不会被克隆)。

my $cpy = shared_clone({'foo' => [qw/foo bar baz/]});

对象状态(即对象被祝福到的类)也会被克隆。

my $obj = {'foo' => [qw/foo bar baz/]};
bless($obj, 'Foo');
my $cpy = shared_clone($obj);
print(ref($cpy), "\n");         # Outputs 'Foo'

对于克隆空数组或哈希引用,也可以使用以下方法

$var = &share([]);   # Same as $var = shared_clone([]);
$var = &share({});   # Same as $var = shared_clone({});

并非所有 Perl 数据类型都可以被克隆(例如,全局变量、代码引用)。默认情况下,如果 shared_clone 遇到此类项目,它将 croak。要将此行为更改为警告,请设置以下内容

$threads::shared::clone_warn = 1;

在这种情况下,undef 将替换要克隆的项目。如果设置为零

$threads::shared::clone_warn = 0;

那么 undef 替换将被静默执行。

is_shared 变量

is_shared 检查指定的变量是否共享。如果共享,则返回变量的内部 ID(类似于 refaddr()(参见 Scalar::Util)。否则,返回 undef

if (is_shared($var)) {
    print("\$var is shared\n");
} else {
    print("\$var is not shared\n");
}

当用于数组或哈希的元素时,is_shared 检查指定的元素是否属于共享数组或哈希。(它不检查该元素的内容。)

my %hash :shared;
if (is_shared(%hash)) {
    print("\%hash is shared\n");
}

$hash{'elem'} = 1;
if (is_shared($hash{'elem'})) {
    print("\$hash{'elem'} is in a shared hash\n");
}
lock 变量

lock 对变量放置一个建议性锁,直到锁超出范围。如果变量被另一个线程锁定,lock 调用将阻塞,直到它可用。同一个线程从动态嵌套作用域内对 lock 的多次调用是安全的——变量将保持锁定,直到对变量的最外层锁超出范围。

lock 严格按照引用一层进行。

my %hash :shared;
my $ref = \%hash;
lock($ref);           # This is equivalent to lock(%hash)

请注意,您不能显式解锁变量;您只能等待锁超出范围。这最容易通过在块内锁定变量来实现。

my $var :shared;
{
    lock($var);
    # $var is locked from here to the end of the block
    ...
}
# $var is now unlocked

由于锁是建议性的,因此它们不会阻止另一个线程访问或修改数据,即使该线程本身没有尝试获取该变量的锁。

您不能锁定容器变量的单个元素。

my %hash :shared;
$hash{'foo'} = 'bar';
#lock($hash{'foo'});          # Error
lock(%hash);                  # Works

如果您需要对共享变量访问进行更细粒度的控制,请参见 Thread::Semaphore

cond_wait VARIABLE
cond_wait CONDVAR, LOCKVAR

cond_wait 函数将一个已锁定的变量作为参数,解锁该变量,并阻塞直到另一个线程对同一个已锁定变量执行 cond_signalcond_broadcast 操作。cond_wait 阻塞的变量在 cond_wait 满足后重新锁定。如果有多个线程在同一个变量上 cond_wait,除了一个线程之外,所有线程都会重新阻塞,等待重新获取该变量的锁。(因此,如果您只使用 cond_wait 进行同步,请尽快放弃锁)。解锁变量和进入阻塞等待状态这两个操作是原子的,而从阻塞等待状态退出和重新锁定变量这两个操作不是原子的。

在它的第二种形式中,cond_wait 接受一个共享的未锁定变量,后面跟着一个共享的已锁定变量。第二个变量被解锁,线程执行被挂起,直到另一个线程发出第一个变量的信号。

需要注意的是,即使没有线程在该变量上执行 cond_signalcond_broadcast,该变量也可能被通知。因此,重要的是检查变量的值,如果条件不满足,则返回等待状态。例如,暂停直到共享计数器降至零

{ lock($counter); cond_wait($counter) until $counter == 0; }
cond_timedwait VARIABLE, ABS_TIMEOUT
cond_timedwait CONDVAR, ABS_TIMEOUT, LOCKVAR

在它的两个参数形式中,cond_timedwait 接受一个已锁定的变量和一个以纪元秒为单位的绝对超时时间(有关详细信息,请参见 perlfunc 中的 time())作为参数,解锁该变量,并阻塞直到超时时间到达或另一个线程发出该变量的信号。如果超时时间到达,则返回一个假值,否则返回一个真值。在这两种情况下,变量在返回时都会重新锁定。

cond_wait 一样,此函数也可以接受一个共享的已锁定变量作为附加参数;在这种情况下,第一个参数是一个未锁定的条件变量,由一个不同的锁变量保护。

cond_wait 类似,唤醒和重新获取锁不是原子操作,因此您应该始终在该函数返回后检查所需条件。但是,由于超时是一个绝对值,因此不必在每次传递时重新计算它。

lock($var);
my $abs = time() + 15;
until ($ok = desired_condition($var)) {
    last if !cond_timedwait($var, $abs);
}
# we got it if $ok, otherwise we timed out!
cond_signal 变量

cond_signal 函数以一个已锁定的变量作为参数,并解除阻塞一个在该变量上cond_wait的线程。如果多个线程在该变量上被阻塞在cond_wait中,则只有一个线程(哪个线程是不确定的)会被解除阻塞。

如果没有线程在该变量上被阻塞在cond_wait中,则信号将被丢弃。通过始终在发出信号之前锁定,您可以(谨慎地)避免在另一个线程进入cond_wait()之前发出信号。

cond_signal 通常会在您尝试在未锁定的变量上使用它时生成警告。在很少情况下,这样做可能是明智的,您可以使用以下方法抑制警告:

{ no warnings 'threads'; cond_signal($foo); }
cond_broadcast 变量

cond_broadcast 函数的工作原理类似于 cond_signal。但是,cond_broadcast 会解除阻塞所有在已锁定变量上被阻塞在cond_wait中的线程,而不是只有一个。

对象

threads::shared 导出一个 bless() 的版本,该版本适用于共享对象,以便祝福在线程之间传播。

# Create a shared 'Foo' object
my $foo :shared = shared_clone({});
bless($foo, 'Foo');

# Create a shared 'Bar' object
my $bar :shared = shared_clone({});
bless($bar, 'Bar');

# Put 'bar' inside 'foo'
$foo->{'bar'} = $bar;

# Rebless the objects via a thread
threads->create(sub {
    # Rebless the outer object
    bless($foo, 'Yin');

    # Cannot directly rebless the inner object
    #bless($foo->{'bar'}, 'Yang');

    # Retrieve and rebless the inner object
    my $obj = $foo->{'bar'};
    bless($obj, 'Yang');
    $foo->{'bar'} = $obj;

})->join();

print(ref($foo),          "\n");    # Prints 'Yin'
print(ref($foo->{'bar'}), "\n");    # Prints 'Yang'
print(ref($bar),          "\n");    # Also prints 'Yang'

注释

threads::shared 被设计为在不可用线程时静默地禁用自身。这允许您编写可以在线程化和非线程化应用程序中使用的模块和包。

如果您想访问线程,则必须在use threads::shared之前use threads。如果您在use threads::shared之后使用use threadsthreads 将发出警告。

警告

cond_broadcast() 在未锁定的变量上调用
cond_signal() 在未锁定的变量上调用

请参阅上面的 "cond_signal 变量"

错误和限制

当在数组、哈希、数组引用或哈希引用上使用share时,它们包含的任何数据都会丢失。

my @arr = qw(foo bar baz);
share(@arr);
# @arr is now empty (i.e., == ());

# Create a 'foo' object
my $foo = { 'data' => 99 };
bless($foo, 'foo');

# Share the object
share($foo);        # Contents are now wiped out
print("ERROR: \$foo is empty\n")
    if (! exists($foo->{'data'}));

因此,在将它们声明为共享后填充此类变量。(标量和标量引用不受此问题的影响。)

在将共享项嵌套到另一个共享项中后,对共享项进行祝福不会将祝福传播到共享引用。

my $foo = &share({});
my $bar = &share({});
$bar->{foo} = $foo;
bless($foo, 'baz');   # $foo is now of class 'baz',
                      # but $bar->{foo} is unblessed.

因此,您应该在共享对象之前对其进行祝福。

除非类本身已编写为支持共享,否则通常不建议共享对象。例如,共享对象的析构函数可能会被多次调用,每次调用都对应于每个线程的范围退出,或者如果它嵌入在另一个共享对象中,则可能根本不会被调用。另一个问题是,由于上述限制,基于哈希的对象的内容将丢失。有关如何创建支持对象共享的类的示例,请参见examples/class.pl(在该模块的 CPAN 分发版中)。

如果这些对象在全局销毁时仍然存在,则可能不会调用对象的析构函数。如果必须调用析构函数,请确保没有循环引用,并且在程序结束之前没有任何内容引用这些对象。

不支持对数组进行splice操作。不支持通过$#array显式更改数组长度 - 请改用pushpop

对共享数组和哈希的元素进行引用不会自动创建元素,对共享数组/哈希进行切片也不会自动创建不存在的索引/键的元素。

share()允许您share($hashref->{key})share($arrayref->[idx]),而不会发出任何错误消息。但是$hashref->{key}$arrayref->[idx]没有被共享,导致在您尝试lock($hashref->{key})lock($arrayref->[idx])时出现错误“锁只能用于共享值”。

使用refaddr()来测试两个共享引用是否等效(例如,在测试循环引用时)是不可靠的。请改用is_shared()

use threads;
use threads::shared;
use Scalar::Util qw(refaddr);

# If ref is shared, use threads::shared's internal ID.
# Otherwise, use refaddr().
my $addr1 = is_shared($ref1) || refaddr($ref1);
my $addr2 = is_shared($ref2) || refaddr($ref2);

if ($addr1 == $addr2) {
    # The refs are equivalent
}

each()在嵌入在共享结构中的共享引用上无法正常工作。例如

my %foo :shared;
$foo{'bar'} = shared_clone({'a'=>'x', 'b'=>'y', 'c'=>'z'});

while (my ($key, $val) = each(%{$foo{'bar'}})) {
    ...
}

以下任一方法都可以代替

my $ref = $foo{'bar'};
while (my ($key, $val) = each(%{$ref})) {
    ...
}

foreach my $key (keys(%{$foo{'bar'}})) {
    my $val = $foo{'bar'}{$key};
    ...
}

此模块支持使用来自 Scalar::Utildualvar() 创建的双值变量。但是,虽然 $! 类似于双值变量,但它被实现为一个绑定的 SV。如果需要,请使用以下结构来传播其值。

my $errno :shared = dualvar($!,$!);

查看现有的错误报告,并提交任何新的错误、问题、补丁等:http://rt.cpan.org/Public/Dist/Display.html?Name=threads-shared

另请参阅

MetaCPAN 上的 threads::shared:https://metacpan.org/release/threads-shared

CPAN 发行版的代码仓库:https://github.com/Dual-Life/threads-shared

threadsperlthrtut

https://perldotcom.perl5.cn/pub/a/2002/06/11/threads.htmlhttps://perldotcom.perl5.cn/pub/a/2002/09/04/threads.html

Perl 线程邮件列表:http://lists.perl.org/list/ithreads.html

此发行版在 CPAN 上的 examples 目录中的示例代码。

作者

Artur Bergman <sky AT crucially DOT net>

文档借鉴自旧的 Thread.pm。

CPAN 版本由 Jerry D. Hedden <jdhedden AT cpan DOT org> 制作。

许可证

threads::shared 在与 Perl 相同的许可证下发布。