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 模块一起使用。
此模块仅支持以下数据类型的共享:标量和标量引用、数组和数组引用以及哈希和哈希引用。
此模块导出以下函数:share
、shared_clone
、is_shared
、cond_wait
、cond_timedwait
、cond_signal
和 cond_broadcast
请注意,如果在 threads 尚未加载时导入此模块,则所有这些函数都将变为无操作。这使得编写可以在线程环境和非线程环境中工作的模块成为可能。
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
接受一个引用,并返回其参数的共享版本,对任何非共享元素执行深层复制。参数中的任何共享元素按原样使用(即,它们不会被克隆)。
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
检查指定的变量是否共享。如果共享,则返回变量的内部 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
严格按照引用一层进行。
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
函数将一个已锁定的变量作为参数,解锁该变量,并阻塞直到另一个线程对同一个已锁定变量执行 cond_signal
或 cond_broadcast
操作。cond_wait
阻塞的变量在 cond_wait
满足后重新锁定。如果有多个线程在同一个变量上 cond_wait
,除了一个线程之外,所有线程都会重新阻塞,等待重新获取该变量的锁。(因此,如果您只使用 cond_wait
进行同步,请尽快放弃锁)。解锁变量和进入阻塞等待状态这两个操作是原子的,而从阻塞等待状态退出和重新锁定变量这两个操作不是原子的。
在它的第二种形式中,cond_wait
接受一个共享的未锁定变量,后面跟着一个共享的已锁定变量。第二个变量被解锁,线程执行被挂起,直到另一个线程发出第一个变量的信号。
需要注意的是,即使没有线程在该变量上执行 cond_signal
或 cond_broadcast
,该变量也可能被通知。因此,重要的是检查变量的值,如果条件不满足,则返回等待状态。例如,暂停直到共享计数器降至零
{ lock($counter); cond_wait($counter) until $counter == 0; }
在它的两个参数形式中,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_wait
的线程。如果多个线程在该变量上被阻塞在cond_wait
中,则只有一个线程(哪个线程是不确定的)会被解除阻塞。
如果没有线程在该变量上被阻塞在cond_wait
中,则信号将被丢弃。通过始终在发出信号之前锁定,您可以(谨慎地)避免在另一个线程进入cond_wait()
之前发出信号。
cond_signal
通常会在您尝试在未锁定的变量上使用它时生成警告。在很少情况下,这样做可能是明智的,您可以使用以下方法抑制警告:
{ no warnings 'threads'; cond_signal($foo); }
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 threads
,threads 将发出警告。
请参阅上面的 "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
显式更改数组长度 - 请改用push
和pop
。
对共享数组和哈希的元素进行引用不会自动创建元素,对共享数组/哈希进行切片也不会自动创建不存在的索引/键的元素。
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::Util 的 dualvar()
创建的双值变量。但是,虽然 $!
类似于双值变量,但它被实现为一个绑定的 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
https://perldotcom.perl5.cn/pub/a/2002/06/11/threads.html 和 https://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 相同的许可证下发布。