• ベストアンサー

ロック処理について

ロック処理について my$id="abc"; open(IN,"file"); while(<IN>){ my ($cid) = split(/\,/); if($cid eq $id){&error;} } close(IN); open(OUT,">>$file"); print "$id,$pass\n"; close(OUT); いままで上記のような書き方でも普通にロック機構を使っていたのですが、 ふと、追加書込みなら必要ないのではと思い至りました。 上記のようにファイルに追加書込みする場合は、ロックは必要ないのでしょうか?

  • Perl
  • 回答数3
  • ありがとう数7

質問者が選んだベストアンサー

  • ベストアンサー
  • N60-BASIC
  • ベストアンサー率80% (17/21)
回答No.3

スクリプトの内容から察するに、質問者さんがやりたいことは 「ユニークな(=既存と重複しない)IDを確実に発行したい」 と理解しました。 回答から先に申し上げると、 「CGI実行環境などの、同一スクリプトが複数プロセスで同時に並行実行される可能性があるのであれば、このケースでは絶対にロックが必要」です。 加えて、このスクリプトの場合、ロックの手段や対象を間違えると正常に動作しません。 まず、ご質問にあった「ロックが必要な理由」から。 他の回答者さんの具体例の通り、複数プロセス間において「書き込みタイミング」が重なった場合に、ロックがないと追記のみであってもデータ破壊が発生します。 すでに挙がっている例とは別のパターンとして、AとBの2つのプロセスが以下のタイミングで並行実行されたとします。 ・Aが追記モードでファイルオープン(ファイルポインタはファイル末尾にセットされる) ・Bが追記モードでファイルオープン(ファイルポインタはファイル末尾=Aと同じ位置!) ・Aがファイルに書き込み(ファイルポインタは書き込み末端位置に移動するが、Bのファイルポインタの位置は変わらない) ・Bがファイルに書き込み(Aが書いたデータを上書きしてしまう!もしAの書き込みデータのほうがBより長かった場合には、結果として「Bのデータ+Aのゴミ」が残る) 「ファイルオープン→ポインタを末尾に移動→書き込み→ファイルクローズ」を一度に行うために、ロックが不可欠なのです。 ロックの必要性についてはご理解頂けたかと思います。 次にロックの方法について。 このスクリプトでは「ファイル読み込み」と「ファイル書き込み」がそれぞれ独立したファイルオープン&クローズで行われています。 この場合、「読み」「書き」それぞれ個別にファイルロックをかけても、意図した動作にはなりません。 「読み」と「書き」の間で、データ内容の一貫性が保たれない場合があるためです。 以下に例を挙げます。 - AがID='abc'の重複をチェックするためにファイルを読み、重複がないことを確認 - BがID='abc'の重複をチェックするためにファイルを読み、重複がないことを確認(この時点でAがまだ書き込んでいないため、Bは重複がわからない!) - AがID='abc'に関する情報を追記書き込み - BがID='abc'に関する情報を追記書き込み(結果、'abc'のレコードが2つに!) つまり、「読み」~「書き」を行う間、他のプロセスに割り込まれないようなファイルロックをする必要があるわけです。これを解決するには、2つの方法があります。 ・データファイルをロック対象にする場合は、「読み」「書き」をひとつのファイルオープンで済ませる ・データファイル以外に「ロック専用ファイル」を用意して、「読み」の前にロック、「書き」の後にアンロックすることで他プロセスの割り込みを防ぐ 例として、前者の方法を取った場合のスクリプト例を以下に示します。 my $id="abc"; use Fcntl; sysopen(my $INOUT, "file", O_CREAT|O_RDWR) or &error; # 読み書きオープン、ファイルがない場合は新規作成 flock($INOUT, 2); # 書き込みlock seek($INOUT, 0, 0); # ファイル先頭に移動 # ID重複チェック while(<$INOUT>){ my ($cid) = split(/\,/); if($cid eq $id){&error;} } seek($INOUT, 0, 2); # ファイル末尾に移動(この例ではすでに末尾にいるので省略可)print $INOUT "$id,$pass"; close($INOUT); # unlockはclose()時に自動で行われる 以上です。以下、補足です。 - open()の代わりにsysopen()を使うことで、読み書きモードによるオープンであってもファイルの新規作成が可能です。 - 今回の例ではファイル先頭から末尾まで読み込むので、seek()は省いても動作すると思います。読まずに追記書き込みのみをする場合はseek(FILEHANDLE, 0, 2)は省略しないでください。ファイルオープンしてからロックするまでの間に、ファイルサイズが変わっていることがあります。 - ファイルハンドルをmyで変数化する癖をつけておくと、将来プログラムが大規模になったときも安心です。INやOUTなどのPerl4風ファイルハンドラは名前空間がグローバルになるので、ファイルオープン中の処理が長くなった場合に予期せぬ動作を招くことがあります。

yuutoOK
質問者

お礼

有難うございます。 私にも理解できるように説明してくださり感謝です。 >>・データファイル以外に「ロック専用ファイル」を用意して、 >>「読み」の前にロック、「書き」の後にアンロックすることで他プロセスの割り込みを防ぐ 普段はこの形式でロックするようにしています。他の人のロックの仕方を真似しただけですが…。 ただ、今までは知識もなかったので、なんとなく使っていただけだったのですが、 今回でいろいろと勉強になりました。 みなさまどうも有難うございました。

その他の回答 (2)

回答No.2

いってることとやっていることの意味がよくわかりませんが。 perl の open 関数やprint系の関数は C言語で言うところのストリーム関数(3S)を用いて出力されているようです。 # fopen, fread, fwrite とか ということで、 ファイルへ出力する段階では確かにロックされますが、 ストリーム関数の場合、ライブラリ内部にバッファを持っていて それがいっぱいになるまではファイルへは出力されません。 なので、あんまり大量のデータを出力するとバッファの出力タイミングがずれるために 出力結果が混ざることになります。 これを本当に回避したいなら、低レベル関数やPOSIX::setvbufを調べてください。 でもまあ、一番よいのは一つのファイルを複数書き込みオープンしないことです。 正確に実施したい場合は、 本物のファイルロック関数を使うかロックファイルを作成して調整してください。

yuutoOK
質問者

お礼

ありがとうございます。 perlの知識がほとんどないので、初歩的で見当違いな質問をしていたみたいです。 出力結果が混ざることもあるということで、勉強になりました。 書込みを行う時は必ずロックするように致します。

回答No.1

一番起こりやすいのが、1行がまざることです。 あるプロセスが"abc.."と追記している間に、別のプロセスがファイルをopenして"ABC"と追記してすぐcloseすると、 abcdABCedfgh... となります。 ちょっとテストコードを書いてみましたが、環境が違うと上手く動作しないかもしれません。私の環境だと ...aaaaa !!! ABC !!! aaaa... となりました。 use strict; use warnings; use Time::HiRes qw(usleep); my $file = 'test.txt'; open my $fh, '>', $file or die "$!:$file"; print {$fh} '---', $/; close $fh or die "$!:$file"; my $pid = fork; if ( !defined $pid ) { die "fork"; } if ( $pid ) { # Parent usleep(1000); open my $fh, '>>', $file or die "$!:$file"; print {$fh} ' !!! ABC !!! ', $/; close $fh or die "$!:$file"; wait; } else { # Child open my $fh, '>>', $file or die "$!:$file"; print {$fh} q{a} x 10000, $/; sleep 1; close $fh or die "$!:$file"; exit; }

yuutoOK
質問者

お礼

有難うございます。 perlの知識はファイルの読み書きコードを何とか覚えた程度なので、ロックといえば、カウンターなどで同時アクセスが起こった場合ファイルが空白になってしまう現象を回避するとしか思っていませんでした。 なので、追加書込みならデーターが消えることがない?ので、ロックの必要はないと質問しました。 データが混ざることもあるんですね。勉強になりました。

関連するQ&A

  • lockについて

    $file = 'file.log'; ------------------------------------ sub a{ &lock;ロック 1: open(IN, $file); # ファイルを開く 2: $count = <IN>; # カウンターを読み出す 3: close(IN); # ファイルを閉じる 4: $count++; # カウンターをひとつ増やす 5: open(OUT, "> $file"); # ファイルを開く 6: print OUT "$count\n"; # ファイルにカウンタを書き込む 7: close(OUT); # ファイルを閉じる 8: print "$count\n"; &unlock;ロック解除 } ------------------------------------ sub b{ 1: open(IN, $file); # ファイルを開く 2: $count = <IN>; # カウンターを読み出す 3: close(IN); # ファイルを閉じる } ------------------------------------ サブルーチンaではロック処理を行っていますが、 サブルーチンbでは書き込む処理がないためロック処理を行っていません。 読み込むファイルは同じでなのですが、 サブルーチンbにもロック処理を入れた方がいいのでしょうか?

    • ベストアンサー
    • Perl
  • CGI Perlでの認証について

    CGIにてログイン認証の処理を使用してみたく勉強がてら Perlにて作成しているのですが、どうもうまくいかない ので質問させてもらいます。 ID・パスワード保存用ファイルfile.datにあらかじめ 100<>p34z7 500<>332bdz といった感じで入力してまして、 ログインボタンを押したときに &login_chk($post{'id'},$post{'pwd'}); (URIデコード処理して$post{'id'},$post{'pwd'}を取得します) を実行して下記サブルーチンを呼び出します。 sub login_chk { my ($in_id, $in_pass) = @_; my ($id, $pass); my $data; if (!open (IN, "./file.dat")) { &Error("ファイルが開けません"); } while ($data = <IN>) { ($id, $pass) = split(/<>/, $data); if ($in_id eq $id){ last; } } close(IN); unless ($in_pass eq $pass) { &Error("正しいID・パスワードを入力してください"); } } ※現状ファイルロックの処理はしてません。  パスワードの暗号化もしてません。 で、IDに100、パスワードにp34z7と入力してログインしようとするのですが 「正しいID・パスワードを入力してください」とエラーが出てきます。 当然500のときも同じくエラーでした。 どこが問題なのでしょうか?昨日から考えてずっとわからないのでご回答よろしくお願いします。

    • ベストアンサー
    • Perl
  • 文字化け対策

    sjisで保存されたファイルを読み込んで、別ファイルに書込みを行ったところ、能等の文字が文字化けを起こしました。 open(IN,"$file"); my $data = <IN>; close(IN); $data=~s/\\//g; open(OUT,">$file2"); print OUT $data; close(OUT); 莫大なソース量だったので、原因を探すのに時間がかかりましたが、一つ一つ消去しながら探したところ、 $data=~s/\\//g; これを消すことで解消しました。 文字化けさせずに\を消すにはどのように書けばいいのでしょうか?

    • ベストアンサー
    • Perl
  • 高速に文字列の比較

    高速に文字列の比較 open(IN,"$file"); while (<IN>) { my($ip,$flag) = split(/,/,$_); if($flag==1){$data .= "$ip,";} } close(IN); open(IN,"$file2"); while (<IN>) { my($ip,$x,$z) = split(/,/,$_); foreach ( split(/,/, $data) ) { if ($ip eq $_) {$xdata.= "$ip,$x,$z\n";} } close(IN); 1.$fileで、$flagが1なら$ipを変数$dataに加えます。 2.$file2の$ipが、$dataの$ipに含まれていれば,$file2のデーターを$xdataに加えます。 上記の書き方で一応比較は出来ているようなのですが、perlの知識があまりない素人が適当に考えた書き方なので、自信がありません。 $data .= "$ip,";の$ipの後ろに,を入れている部分も何だか変なのですが、他の書き方が思いつけません。 書き方が間違っている場合や、こうすればもっと高速に動作させられるという書き方があれば、ご指導いただけると幸いです。 $fileも$file2も、100万行あたりまで増える見込みなので、出来るだけサーバーに負荷のかからない書き方ができればと思っています。 お手数ですが、どうぞよろしくお願いいたします。

    • ベストアンサー
    • Perl
  • CSVデータの同じファイルに上書きするには。

    CSV形式でデータdata.csvが書いてあります。プログラムを実行して、そのファイルの$data[4]の値が5という数字だった場合は、そこのセルだけ"解除"という文字に置き換えて(ほかに入ってる値ははそのまま)data.csvに上書きしたいのですが、どうもうまくいきません。したのように記述したのですが、どこが間違っているのかがわかりません。。どなたか教えてください。よろしくおねがいします。 #!/usr/bin/perl $file='data.csv'; open(FILE, "$file"); while(<FILE>){ @data = split(/,/, $_); } close(FILE); if($data[4] eq "5"){$data[4] = "解除";} open(OUT, ">$file"); print OUT @data; close(OUT);

  • 排他制御のためのロックについて

    &lock; #--->ロック処理 open(IN,"data.txt"); $data = <IN>; close(IN); &unlock; #--->アンロック処理(1) $data = $ENV{'REMOTE_ADDR'}.\n;#-->(2) &lock; #--->ロック処理(3) open (OUT,">>data.txt"); print OUT "$data"; close (OUT); &unlock; #--->アンロック処理 ということをした場合、一度目のアンロック処理((1))と、二度目のロック処理((3))の間は何もロックはしてないことになりますよね? もし、仮に(1)~(3)の処理をするのに3秒もかかるとします。 そのとき、 ・AとBの2人が別々のIPを持った状態で、上記が記述されたCGIに1秒差でアクセスして、「(2)」が2人によって1秒違いで実行される。 という場合では、二人の「$data」の値は同じIPアドレスになる(最初に実行した方が、後で実行された$dataの値によって上書きされる)のでしょうか? それとも、$dataの二人とも別々の本来のIPアドレスがdata.txtに書き込まれるのでしょうか? もし、同じIPアドレスになったとしたら、open~closeまでをロックするというのでは駄目なのでしょうか?

    • ベストアンサー
    • Perl
  • 掲示板を全削除機能にしたいのですが

    現在使っている掲示板は投稿を個別に削除は出来るのですが、一括削除は出来ません。一括削除だけしたいのですが、一括削除できる定型文のようなものはあるのでしょうか?よろしくお願いします。(Iモード用BBSです。本CGIスクリプトのメンテ部分です。) sub mainte { my($del) = (0); if($form{'pass'} ne $mainte_pass){ &error ("bad password."); } $out_val{'pass'} = $form{'pass'}; if($form{'id'}){ # delete if(&lock($log_file, 5)){ open (IN, "< $log_file") or &error ("can't open $log_file to read. $!"); open (OUT, "> $log_file.$$") or &error ("can't open $log_file.$$ to write $!"); $del = 0; while(<IN>){ if(/^$form{'id'}\t/){ $del = 1; next; }else{ print OUT $_; } } close IN; close OUT; if($del){ rename $log_file, "$log_file.bak"; rename "$log_file.$$", $log_file; chmod 0666, $log_file; $msg = qq|$form{'id'}番の投稿を削除しました。<BR>\n <A href="$out_val{'cgi_name'}">戻る</A>|; &error($msg); }else{ unlink "$log_file.$$"; $msg = qq|$form{'id'}番の投稿は存在しません。<BR>\n <A href="$out_val{'cgi_name'}">戻る</A>|; &error($msg); } } }else{ if(&lock($log_file, 5)){ open (IN, "< $log_file") or &error ("can't open $log_file to read. $!"); while(<IN>){ ($id, $time, $name, undef, $title ) = split "\t"; $out_val{'del_list'} .= qq|$id, $time, $name, $title<BR>\n|; } &page_out($mainte_page); } } unlock($log_file); return 1; }

    • ベストアンサー
    • CGI
  • レコードの書込み判断

    ファイルを読込み、読込まれたレコードの39桁目が0であれば出力という判断を下記のコーディングに追加したいのですが、どのように記述すればよいでしょうか? if(open(OUT,">$year$mon$mday$csv")){  for my $fname (sort @dirs){   if(open(IN,"$fname")){   my @lines = <IN>;   print OUT @lines;   close(IN);   }  }  close(OUT); }

  • 連続投票の制限

    投票cgiを設置したいのですがうまくいきません、集計を1日一回だったのを少し改造して即時集計にしました 一応動くのですが、連続投票できてしまいます。 連続投票を一定時間制限できるようにしたいのですがうまくいきません 60秒くらい間をおいてから投票できるようにできますか? ip制限もあるみたいなのですがこれも機能してないです・・・ 改善できるなら元のソースがかなり変わってもかまいません 連続投票制限だけでも機能できればと思っています。よろしくお願いします local $times = time(); sub vote{ if($FORM{id}){ my $fl=0; if(!$CK_ref){$fl=1;} else{ if(index($ENV{'HTTP_REFERER'},index.html>=0){$fl=1;} } if($fl){ $FORM{id}=~s/\n//g; my $vote = $FORM{vt2}?1:0; open(OUT,">>vote_temp.cgi"); #時間用のログファイルに書き込み print OUT "$FORM{id}<>$ENV{'REMOTE_ADDR'}<>$FORM{vt}<>$vote<>".$times."<>\n"; close(OUT); &reset_vote if $VT_RESET < $times; my(@log_lines,%pt,%pt2,%cnt,$name,$value,%CKIP); open(IN,"vote_temp.cgi"); my @log = <IN>; close(IN); if(@log){ my $cktime = $times - 60; #60秒制限 open(IN,"vote_ck_IP.cgi"); #投票した人のIPを記録したログ while(<IN>){ my @ck = split('<>'); next if $_[1] < $cktime; $CKIP{$_[0]} = $_[1]; } close(IN); foreach(@log){ chop; my @rank = split('<>'); #ID<>IP<>評価<>おすすめ<>時間\n next if $CKIP{"$rank[0]_$rank[1]"}; $pt{$rank[0].'_'.$rank[2]}++; $pt2{$rank[0]}++ if $rank[3]; $cnt{$rank[0]}++; $CKIP{"$rank[0]_$rank[1]"} = $rank[4]; } open(OUT,">vote_temp.cgi"); close(OUT); open(OUT,">vote_ck_IP.cgi"); while(($name, $value) = each(%CKIP)){ print OUT "$name<>$value<>\n"; } close(OUT); open(IN,"log.cgi"); my @data = <IN>; close(IN); foreach(@data){ #集計処理長かったので省略 } open(OUT,">vote_bf.cgi"); open(IN,"vote_log.cgi"); while(<IN>){ print OUT $_; } close(IN); close(OUT); open(OUT,">vote_log.cgi"); print OUT @log_lines; print OUT "\n1;\n"; close(OUT); } &make_vote_ck('set'); } } sub reset_vote{ my @log; foreach my $i(1..$LAST_ID){ next if !@{$VT[$i]}; $VT[$i][18] = $VT[$i][7]; $VT[$i][19] = $VT[$i][8]; $VT[$i][20] = $VT[$i][0]; foreach my $j(0..8){ $VT[$i][$j] = 0; } push(@log,'$VT['.$i.'] = ['.(join(',',@{$VT[$i]}))."];\n"); } open(OUT,">vote_log.cgi"); print OUT @log; print OUT "\n1;\n"; close(OUT); &make_vote_ck('reset'); } sub make_vote_ck{ if($_[0] eq 'set'){ my @t =localtime($times + 86400); $VT_TIME = timelocal(0,0,5,$t[3],$t[4],$t[5]); $VT_RANK = $VT_RUI = $VT_RECO = $VT_RCRUI = $VT_COUNT = $VT_CTRUI = $VRK_CK = 1; } if($_[0] eq 'reset'){ my @m =localtime($times); $m[4] += 1; if($m[4] > 12){ $m[4] = 1; $m[5] += 1; } $VT_RESET = timelocal(0,0,2,1,$m[4],$m[5]); $VT_RANK = $VT_RECO = $VRK_RS = $VRK_CK = 1; } if($_[0] eq 'restore'){ $VT_RANK = $VT_RUI = $VT_RCRUI = $VT_RECO = $VT_COUNT = $VT_CTRUI = $VRK_CK = 1; } open(OUT,">vote_ck.cgi"); print OUT "\$VT_TIME = '".$VT_TIME. "';\n"; print OUT "\$VT_RESET = '".$VT_RESET. "';\n"; print OUT "\$VT_RANK = '".$VT_RANK. "';\n"; print OUT "\$VT_RUI = '".$VT_RUI. "';\n"; print OUT "\$VT_RECO = '".$VT_RECO. "';\n"; print OUT "\$VT_RCRUI = '".$VT_RCRUI. "';\n"; print OUT "\$VT_COUNT = '".$VT_COUNT. "';\n"; print OUT "\$VT_CTRUI = '".$VT_CTRUI. "';\n"; print OUT "\$VRK_CK = '".$VRK_CK. "';\n"; print OUT "\$VRK_RS = '".$VRK_RS. "';\n"; print OUT "\$LAST_ID = '".$LAST_ID. "';\n"; print OUT "\n1;\n"; close(OUT); }

    • ベストアンサー
    • Perl
  • 文字コードの変換(Shift-JISからUTF8)

    文字コードがShift-JISのCSVファイルを読み込み、UTF-8のテキストファイルに出力するのに プログラムの中で変更しようとしているのですが、うまくいきません。出力ファイルの文字コードを 確認するとShift-JISのままです。 どなたか教えていただけないでしょうか? ActivePerl v5.16.0を使用し、Encodeモジュールのfrom_toを使用しています。 #!/usr/bin/perl use strict; use warnings; use utf8; use Encode; my $input_file="input.csv"; my $output_file="output.txt"; open (IN, $input_file) or die "$!"; open (OUT, ">$output_file") or die "$!"; while (<IN>){ chomp ($_); my @data=split(/,/,$_); for(my $i=0;$i<@data;$i++){ $data[$i]=Encode::from_to($data[$i],'shiftjis','utf8'); #Shift-JISからUTF-8に変換 $data[$i]=~s/\s+//g; print OUT $_; } print OUT "\n"; } close (IN); close (OUT);

    • ベストアンサー
    • Perl

専門家に質問してみよう