業務とは離れたことをほんとは書きたいのですが、半日つぶしちゃったのでせっかくなのでメモ。
perlのスレッド(ithreads)について。
ithreadsはスレッド生成時にすべての変数をコピーし、Copy on Write※は行われません (C/C++から使えるpthreadsだとスタック以外のメモリ領域は暗黙的に共有され、コピーは行われない)。変数をスレッド間で共有したい場合は、宣言時にthreads::sharedのshared属性を明示的に付ける必要があります。ちなみに、C/C++でOpenMPを使う場合、共有したい変数にshared指示節を付けますが、これを省略すると変数は暗黙的に共有されます。
※ Copy on Write
共有しているメモリ空間に更新が発生するときに初めてコピーが行われ、更新された箇所のみ親と子で別々の物理メモリ空間が割り当てられる。
* 例
[perl]
#!/usr/bin/perl
use strict;
use warnings;
use threads;
use threads::shared;
my $var : shared = 10; # $var をスレッド間で共有する
my $th = threads->new(
sub {
while(1) {
printf(“[TID: %d] %d\n”, threads->self->tid, $var);
$var *= 2;
sleep 1;
}
}
);
while(1) {
printf(“[TID: %d] %d\n”, threads->self->tid, $var);
$var += 1;
sleep 1;
}
$th->join;
# 結果
# [TID: 0] 10
# [TID: 1] 11
# [TID: 0] 22
# [TID: 1] 23
# [TID: 0] 46
# [TID: 1] 47
# [TID: 0] 94
# …
[/perl]
ithreadsは前述のようにスレッド生成時にすべての変数のコピーを行うのでコストが高いです。なので、もしこれでサーバを書く場合はper-threadな設計は止めた方がいいということですね。
cond_signal() / cond_wait()
スレッドの同期に使うcond_wait()/cond_signal()の使い方。
・ 例
produce側ではまずキューをロック、そこに数値をpushしてcond_signal()を呼び出しています。このcond_signal()は別スレッドにキューを操作したことを知らせます。consume側ではまずキューをロック、キューが空の時はcond_wait()でロックを解放して待ち状態になって休み、キューに値が入っている時はそこから値をpopして計算後、出力を行っています。
[perl]
#!/usr/bin/perl
use strict;
use warnings;
use threads;
use threads::shared;
my @queue : shared;
threads->new(\&produce);
threads->new(\&consume);
$_->join for threads->list;
sub produce {
while(1) {
{
lock @queue;
push @queue, int(rand(10));
cond_signal(@queue);
}
sleep 1;
}
}
sub consume {
while(1) {
lock @queue;
while(@queue == 0) {
cond_wait(@queue);
}
my $i = pop @queue;
printf “$i * $i = %d\n”, $i * $i;
}
}
# 結果
# 0 * 0 = 0
# 1 * 1 = 1
# 5 * 5 = 25
# 3 * 3 = 9
# …
[/perl]
僕自身は業務でithreadsを使ったことはないのですが、他の人が書いたコードを読む時の前提知識としてメモしておきました。
* 参考
WEB+DB PRESS vol.42