threads::shared - スレッド間でデータ構造を共有するための Perl エクステンション
このドキュメントは threads::shared バージョン 1.14 を記述しています。
use threads;
use threads::shared;
my $var :shared;
$var = $scalar_value;
$var = $shared_ref_value;
$var = share($simple_unshared_ref_value);
my ($scalar, @array, %hash);
share($scalar);
share(@array);
share(%hash);
my $bar = &share([]);
$hash{bar} = &share({});
{ 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
, cond_wait
, cond_timedwait
, cond_signal
, cond_broadcast
, is_shared
まだ threads がロードされていない段階でこのモジュールをインポートすると、 その機能は全て無効になることに注意してください。 これによりスレッド・非スレッド環境の両方で動作するモジュールを 書くことができます。
share
は値を引数に取り、それを共有化されたものとしてマークします。 スカラー、配列、ハッシュ、スカラーリファレンス、配列リファレンス、あるいは ハッシュリファレンスを共有化することができます。 share
は共有化された右辺値(rvalue)を返しますが、それは常に リファレンスとして返されます。
:shared
属性( my $var : shared;
)を使うことで、ある変数を コンパイル時に共有化されたものとしてマークすることもできます。
Perl のプロトタイプ宣言に伴う問題で、もし新たに生成したリファレンスを 共有したい場合、&share([])
と&share({})
という構文を 使う必要があります。
共有したスカラに代入できる値は、他のスカラ値か、共有した リファレンスだけです:
my $var :shared;
$var = 1; # ok
$var = []; # error
$var = &share([]); # ok
share
はリファレンスを正確に 1 レベルだけ調べます。 share(\$a)
は share($a)
と等価ですが、share(\\$a)
は違います。 これは、ネストした共有データ構造は、まずそれぞれの共有葉オブジェクトを作成し、 それからそれらを共有ハッシュやアレイに追加することによって 作成しなければならないことを意味します。
my %hash :shared;
$hash{'meaning'} = &share([]);
$hash{'meaning'}[0] = &share({});
$hash{'meaning'}[0]{'life'} = 42;
is_shared
は、指定された変数が共有されているかどうかをチェックします。 共有されていれば、(refaddr() と同様に) 変数の 内部 ID を返します。 そうでなければ、undef
を返します。
if (is_shared($var)) {
print("\$var is shared\n");
} else {
print("\$var is not shared\n");
}
lock
はスコープから外れるまで変数をロックします。 もし他のスレッドによってその変数がロックされているなら、ロックが 可能になるまで lock
の呼び出しはブロックされます。 動的にネストしたスコープの中から同じスレッドによって 複数回 lock
を呼び出しても安全です -- 最も外側のロックがスコープから 抜けるまでその変数はロックされ続けます。
ハッシュや配列といったコンテナオブジェクトがロックしても、 その要素はロックされません。 例えば、あるスレッドが lock(@a)
するとしても、他のスレッドの行う lock($a[12])
はブロックされません。
lock
は正確に 1 レベルリファレンスを辿ります。 lock(\$a)
は、 lock($a)
と等価ですが、lock(\\$a)
とは 等価ではありません。
明示的に変数を unlock することはできないことに注意してください; ロックがスコープを抜けるのを待つしかありません。 これはブロックの内側で変数をロックすることで最も簡単に達成できます。
my $var :shared;
{
lock($var);
# $var is locked from here to the end of the block
...
}
# $var is now unlocked
もし共有変数へのアクセスについてより精度の高い制御を望むなら、 Thread::Semaphore を参照してください。
cond_wait
関数は ロックされた 変数を引数に取り、その変数のロックを 解除します。 そして他のスレッドがその同じロックされていた変数に向けて cond_signal
か cond_broadcast
するまで、ブロック(待機)します。 cond_wait
がブロックする変数は、cond_wait
が完了した後、再度 ロックされます。 もし複数のスレッドが同じ変数に対して cond_wait
しているなら、 一つを除いて全てのスレッドがロックを獲得するまで待機するために再度 ブロックします (よって同期のために cond_wait
を使うだけなら、可能な限り 早くロックを解除してください)。 変数のロック解除と、ブロックされて待ち状態に入るという二つの動作は アトミックです。 待ち状態から抜けることと、変数の再ロックという二つの動作は、 アトミックではありません。
第二の書式では、cond_wait
は ロックされていない 共有変数をとり、 その後ろに ロック された共有変数がきます。 この二番目の変数はロックが解除され、そして他のスレッドが一番目の変数に シグナルを送るまで、そのスレッドの実行は停止します。
どのスレッドも、変数に対し cond_signal
や cond_broadcast
をしなくても、 その変数は notify されうるということに注意することが重要です。 それゆえ、変数の値のチェック及び、要求が満たされない場合に待ち状態へ 戻ることが重要です。 例えば、共有カウンタが 0 になるまで停止するには:
{ lock($counter); cond_wait($count) until $counter == 0; }
二つの引数をとる形式では、cond_timedwait
は ロックされた 変数と 絶対的なタイムアウト時間を引数にとります。 変数はロック解除され、タイムアウト時間に達するか、他のスレッドが変数に シグナルを送るかするまでブロックされます。 タイムアウトになると偽の値が返されます。 そうでなければ真の値が返されます。 どちらの場合でも戻りの際に変数は再ロックされます。
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 がスレッドをまたいで伝搬するような 共有オブジェクトで動作するバージョンの bless() を エクスポートします。
# Create a shared 'foo' object
my $foo;
share($foo);
$foo = &share({});
bless($foo, 'foo');
# Create a shared 'bar' object
my $bar;
share($bar);
$bar = &share({});
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 が利用できない場合、threads::shared は黙って利用不可になるよう 設計されています。 threads にアクセスしようとするなら、use threads::shared
する前に use threads
しなければなりません。 threads::shared の後に threads を use しようとすれば、 警告が発せられます。
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'}));
従って、このような変数は、共有すると宣言した 後に 設定してください。 (スカラとスカラリファレンスはこの問題の影響を受けません。)
クラス自身が共有に対応するように書かれていないオブジェクトを共有することは たいてい賢明なこととは言えません。 例えば、各スレッドのスコープから外れる毎に、あるオブジェクトの デストラクタが複数回呼び出されるかも知れません。 もう一つの危険性は、ハッシュベースのオブジェクトの内容が、 上述した制限のために失われてしまうことです。 オブジェクト共有に対応したクラスの作り方については、(このモジュールの CPAN 配布に含まれている examples/class.pl を参照してください。
配列に対する splice
はサポートしていません!
共有化された配列とハッシュの要素へのリファレンスをとっても 自動有効化しません。 また、共有配列/ハッシュで存在しないインデックスや キーにスライスしても、その要素は自動有効化しません。
share($hashref->{key})
しても、share()
はエラーメッセージを 出しません。 しかし、$hashref->{key}
は共有 されません。 lock($hashref->{key})
しようとすれば "locking can only be used on shared values" (ロックは共有変数に対してのみ使用できます) というエラーが発生します。
既に報告されているバグの閲覧や、新しいバグ、問題、パッチなどの投稿先: http://rt.cpan.org/NoAuth/Bugs.html?Dist=threads-shared
CPAN の threads::shared ディスカッションフォーラム: http://www.cpanforum.com/dist/threads-shared
threads::shared の注釈付き POD: http://annocpan.org/~JDHEDDEN/threads-shared-1.14/shared.pm
ソースレポジトリ: http://code.google.com/p/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.cpan.org/showlist.cgi?name=iThreads
Artur Bergman <sky AT crucially DOT net>
threads::shared is released under the same license as Perl.
Documentation borrowed from the old Thread.pm.
CPAN version produced by Jerry D. Hedden <jdhedden AT cpan DOT org>.