ルモーリン
ホーム 更新 Perl Sample サービス 雑談 鉄ゲタ Linux リンク 連絡先

セマフォを使ってみました

2005-12-30

背景

自分のホームページのどれにアクセスがあるのか知りたくなりました。 しかしながらプロバイダのカウンタは5ページしか設定できません。 そこでホームページカウンタを作って、自宅のサーバで稼働させ、全部のページに設置しました。 テキストファイルの中に見られたページのURLと回数を入れるようにしたので、2人が同時にうちのホームページを見に来ると、テキストファイルの更新漏れが発生します。 それを防ぐものを捜したところ「セマフォ」が有用であることがわかりました。 しかしながら、googleで簡単に検索してみると、具体的な使用例を見つけられなかったので、ここに紹介します。 間違えていたら、エンリョなくご指摘ください。

準備

セマフォを作ってsemvalを1にします。Linuxを起動する度に1回だけ実行します。
init.pl
#!/usr/bin/perl -w

$IPC_KEY = 1234;
$IPC_CREATE = 0001000;
$id = semget($IPC_KEY, 1, 0666 | $IPC_CREATE);
print "semget(create) id = $id\n";
$id = semget($IPC_KEY, 0, 0);
print "semget(get)    id = $id\n";

$semop = pack("sss", 0, 1, 0);
semop($id, $semop);

確認

排他制御ができてるか確認してみます。test1.plはsemvalから1を引いて10秒間待って1を足します。 1個目のtest1.plを起動して「end -1」が表示されたのを見てから、2個目のtest1.plを起動すると「start -1」が表示された所で停止します。 1個目のtest1.plの「end -1」の表示から10秒後に「end 1」が表示されると同時に2個目のtest1.plから「end -1」が表示されます。 これで、test1.plの1番目のsemopから2番目のsemopまでの間は1個の処理だけ(=排他)となりました。
test1.pl
#!/usr/bin/perl -w

$IPC_KEY = 1234;
$id = semget($IPC_KEY, 0, 0);
print "get    id = $id\n";

$| = 1;
print "start -1\n";
$semop = pack("sss", 0, -1, 0);
semop($id, $semop);
print "end -1\n";

sleep(10);

print "start 1\n";
$semop = pack("sss", 0, 1, 0);
semop($id, $semop);
print "end 1\n";

使い方

CGIプログラムで利用する場合は、こんな感じになります。 下記の中でのlogupdateはテキストファイルを更新するサブルーチンです。 これを2個のsemopで挟むことでlogupdate(テキストファイルの更新処理)が必ず1度に1個だけとなります。
accesslog.cgi(の一部)
(前処理)

$IPC_KEY = 1234;
$id = semget($IPC_KEY, 0, 0);

$semop = pack("sss", 0, -1, 0);
semop($id, $semop);

logupdate($referer, $logfull, $matchbase);

$semop = pack("sss", 0, 1, 0);
semop($id, $semop);

(logupdateや他のサブルーチンを定義)

おまけ

現在のsemvalを表示するスクリプト 2005.12.30 /proc/sysvipc/semにセマフォの一覧があるのを知ったのでこれはもう要りません。
#!/usr/bin/perl -w

$GETVAL = 12;
$IPC_KEY = 1234;

$id = semget($IPC_KEY, 0, 0);
print "semget id = $id\n";

my $junk = 0;
my $semval = semctl($id, 0, $GETVAL, $junk);

print "semctl semval = $semval\n";

セマフォってこんな感じ

私が使う分での話ですけれど、semvalの1を取り合っているようなものですね。 「$semop = pack("sss", 0, -1, 0); semop($id, $semop);」で1を手に入れて(他の人=タスクが手に入れていれば、手に入るまで待ちます)肝心のやりたい事をやってから「$semop = pack("sss", 0, 1, 0); semop($id, $semop);」で1を手放します。 あ、今このページを書いていてinit.plでsemopやってるけどsemctlのSETVALが自然だと気がつきました。