アプリ版:「スタンプのみでお礼する」機能のリリースについて

プログラムのアクセスカウンターをするプログラムを作成しましたが、アクセスカウンターのデータが消去されてしまいます。


プログラム抜粋
(ここから)
#アクセスカウンターカウント数計上ロジックを入れる
$data = "";
open(IN11,"$countcsv") || &error(" countcsv をopen出来ません");
flock(IN11,1);
while($lines11 = <IN11>) {
($seq,$acc) = split("\,", $lines11);
if ($page == $seq) {$acc = $acc +1; }
$data .= "$seq,$acc\n"
}
close(IN11);
open(IN21,">$countcsv")|| &error(" countcsv をopen出来ません");
flock(IN21,2);
print IN21 "$data";
close(IN21);
(ここまで)

count.csvの構成は以下のようになってます。
(ここから)
1,0
2,0
3,0
4,0
5,0
6,0
7,0
8,0
9,0
10,0
(ここまで)

カウンターを計測するデータ(行数)が多いと、データが全てなくなってしまいます。

ファイルロックの方法に問題があると思ってますが、どこが悪いのかがわかりません。

ご教授いただけますと助かります。

よろしくお願いいたします。

A 回答 (2件)

問題点は4つあります。


今回の不具合においては1と2が致命的。
3は比較的軽微なバグ。
4以降は推奨される書き方です。

1.読み込み処理と書き込み処理が分かれているのにロックを個別に行っている。

「データを読む~データを改変する~データを書く」を一度のロックで行わないと
途中で他のプロセス処理が割り込む可能性がある。その結果、複数のプロセスが
矛盾するデータを書き込もうとした場合にいずれかのデータが失われる。
回避方法としては、下記のいずれかが有効。
・1つのファイルに対する読み書きの場合は1回のオープン+ロックで済ませる。
・複数ファイルを同時に処理する必要がある場合には、ロック用ファイルを用意し
これに対してロックを行ってからすべての処理を行う。
あるいは、処理の順番的に必ず最初にオープンするデータファイルがある場合は
これに対してロックをかけ、すべての処理を行ってから最初のファイルを最後に
クローズすることでロック用ファイルを兼ねる。


2.(上記と併せて)書き込み処理時に上書きモード'>'でオープンしている。

上書きモードでは、ファイルオープン時にファイル内容は一度すべて消去される。
この状態で1.の状況が発生した場合、ちょうど消去されたばかりのファイルを
別プロセスが読み込むために、既存データが消えることになる。
ファイルロックはファイルオープンとは同時には行うことができないため、
ロックがかかるまでに時差が発生することに注意する。


3.1行読み出し $lines11 = <IN11> において、末尾の改行を削除していない

$accに改行がついたままになっている。$accに変更があった行だけは数値代入の
副作用で改行が外れるが、変更がなかった行は改行がついたままの状態で
書き込み時にさらに改行が付加されるため、データに意図しない空行が発生する。


4.Perl4の文法で記述されている。

Perl4の文法でも、正しく記述すればこれ自体が不具合を起こすことはないが、
プログラムが大規模になるほどスクリプトのデバッグが難しくなる。
Perl5では、以下のような機能が用意されている。
・宣言されていない変数などを文法エラーにする use strict; プラグマ
変数名のタイプミスを検出できる
・未定義値に対する演算などに警告を出す use warnings; プラグマ
変数の初期値設定忘れを防ぐ。ハッシュキーの打ち間違いが見つかることも。
・ブロック内でのみ使用できるローカル変数の宣言 my
変数名の重複による誤動作等を防ぎ、変数の局所化によって見通しが良くなる
・ファイルハンドルにもmy変数を使える
Perl4ではグローバル変数としてしか扱えなかったファイルハンドルを局所化
できる。スクリプト内でファイルハンドルが重複することによる誤動作を防ぐ。


上記を踏まえて修正したスクリプトは以下になります:

sub count_up {
my($countcsv, $page) = @_;
use Fcntl;
sysopen(my $fh, $countcsv, O_CREAT|O_RDWR) or error(" countcsv をopen出来ません");
flock($fh, 2);
seek($fh, 0, 0);
my $data = '';
while(my $line = <$fh>) {
chomp $line;
my($seq, $acc) = split /,/, $line;
if($page == $seq) { $acc++; }
$data .= "$seq,$acc\n";
}
seek($fh, 0, 0);
print $fh $data;
truncate($fh, tell($fh));
close($fh);
}

count_up($countcsv, $page);

この回答への補足

アドバイスありがとうございます。
丁寧な回答に感謝いたします。
まだ内容を全て理解できていないところがございますが実践してみます。
確認後お礼をいれます。

補足日時:2011/04/29 08:08
    • good
    • 0
この回答へのお礼

お礼が遅れました。
最初は正常に動作しませんでしたが、やっと動作するようになりました。
ありがとうございます。
また基本的な考え方についての間違い指摘にも感謝いたします。

お礼日時:2011/05/02 00:24

ちょっと調べた.



参考URL:http://homepage1.nifty.com/glass/tom_neko/web/we …
    • good
    • 0
この回答へのお礼

ありがとうございます。
内容を確認しいろいろとやってみます。

お礼日時:2011/04/29 08:06

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!