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 上的偽分岔)之間共用變數。它與 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
會傳回共用的 rvalue,但永遠是參照。
變數也可以在編譯時期使用 :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 資料類型都可以被複製(例如:glob、程式碼參考)。預設情況下,如果 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
會取得一個已鎖定的變數和一個以epoch 秒為單位的絕對逾時時間(詳情請參閱 perlfunc 中的 time()) 作為參數,解鎖該變數,並封鎖直到逾時或另一個執行緒對該變數發出訊號。如果逾時,則傳回 false 值,否則傳回 true 值。不論哪種情況,在傳回時都會重新鎖定該變數。
與 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
。如果您在 threads::shared 之後使用 threads,threads 會發出警告。
請參閱上方的 "cond_signal VARIABLE"。
當 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
http://www.perl.com/pub/a/2002/06/11/threads.html 和 http://www.perl.com/pub/a/2002/09/04/threads.html
Perl threads 郵件列表: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 相同的授權釋出。