Perl でファイルをロックするにはどうすればよいですか?
質問
Perl でファイルにロックを作成する最良の方法は何ですか?
ファイルに群がるのが最善でしょうか、それともロック ファイルを作成してロックを設定し、ロック ファイルのロックを確認するのが最善でしょうか?
解決
flock を使用することになった場合は、次のコードを使用します。
use Fcntl ':flock'; # Import LOCK_* constants
# We will use this file path in error messages and function calls.
# Don't type it out more than once in your code. Use a variable.
my $file = '/path/to/some/file';
# Open the file for appending. Note the file path is quoted
# in the error message. This helps debug situations where you
# have a stray space at the start or end of the path.
open(my $fh, '>>', $file) or die "Could not open '$file' - $!";
# Get exclusive lock (will block until it does)
flock($fh, LOCK_EX) or die "Could not lock '$file' - $!";
# Do something with the file here...
# Do NOT use flock() to unlock the file if you wrote to the
# file in the "do something" section above. This could create
# a race condition. The close() call below will unlock the
# file for you, but only after writing any buffered data.
# In a world of buffered i/o, some or all of your data may not
# be written until close() completes. Always, always, ALWAYS
# check the return value of close() if you wrote to the file!
close($fh) or die "Could not write '$file' - $!";
いくつかの役立つリンク:
追加の質問に答えると、ファイルにロックを設定するか、ファイルがロックされているときは常に「lock」と呼ぶファイルを作成し、ロックが解除されたらそれを削除します(そして、プログラムが従うことを確認してください)それらのセマンティクス)。
他のヒント
他の回答は Perl のフロック ロックをかなり詳しくカバーしていますが、多くの Unix/Linux システムでは実際には 2 つの独立したロック システムがあります。BSD flock() および POSIX fcntl() ベースのロック。
Perl を構築するときに特別なオプションを設定しない限り、そのフロックは利用可能な場合は flock() を使用します。これは通常は問題なく、アプリケーション (単一システム上で実行されている) 内でロックする必要があるだけの場合に必要な機能と思われます。ただし、場合によっては、fcntl() ロックを使用する別のアプリケーション (多くのシステム上の Sendmail など) と対話する必要がある場合や、NFS マウントされたファイル システム間でファイル ロックを実行する必要がある場合があります。
そのような場合は、次のことを確認するとよいでしょう。 ファイル::FcntlLock または ファイル::lockf. 。純粋な Perl で fcntl() ベースのロックを実行することもできます (pack() の一部の毛深く移植不可能な部分を使用)。
flock/fcntl/lockf の違いの概要:
lockf はほとんどの場合 fcntl の上に実装され、ファイル レベルのロックのみを持ちます。fcntl を使用して実装された場合、以下の制限が lockf にも適用されます。
fcntl は、範囲レベルのロック (ファイル内) と NFS 経由のネットワーク ロックを提供しますが、ロックは fork() の後に子プロセスに継承されません。多くのシステムでは、共有ロックを要求するにはファイルハンドルを読み取り専用でオープンし、排他ロックを要求するには読み取り/書き込み可能でファイルハンドルを開く必要があります。
flock にはファイル レベルのロックのみがあり、ロックは単一マシン内でのみ行われます (NFS マウントされたファイルをロックできますが、ロックはローカル プロセスのみが参照します)。ロックは子に継承されます (ファイル記述子が閉じられていないと仮定します)。
場合によっては (SYSV システム) flock は lockf または fcntl を使用してエミュレートされます。一部の BSD システムでは、lockf は flock を使用してエミュレートされます。一般に、この種のエミュレーションはうまく動作しないため、避けることをお勧めします。
CPAN が救助します: IO::LockedFile.
ライアン P は次のように書きました。
この場合、実際には、ファイルが再度開かれる間の短期間、ファイルのロックが解除されます。
だからそんなことはしないでください。その代わり、 open
読み取り/書き込み用のファイル:
open my $fh, '+<', 'test.dat'
or die "Couldn’t open test.dat: $!\n";
カウンタを作成する準備ができたら、次のようにします。 seek
ファイルの先頭に戻ります。それを行う場合は、次のようにする必要があることに注意してください。 truncate
直前 close
, これにより、新しい内容が以前の内容よりも短い場合でも、ファイルの末尾にゴミが残らないようになります。(通常、ファイル内の現在位置は最後にあるため、次のように書くことができます。 truncate $fh, tell $fh
.)
また、3 つの引数を使用したことに注意してください open
と字句ファイル ハンドル、そして操作の成功もチェックしました。グローバル ファイル ハンドル (グローバル変数はダメですよね?) やマジック 2 引数は避けてください。 open
(これは Perl コードの多くの悪用可能なバグの原因となっています)、常に、 open
成功する。
語彙変数をファイルハンドラーとエラー処理として表示する方がずっと良いと思います。また、すべてのオペレーティング システムで正しい数字であるとは限らないマジック ナンバー 2 をハードコードするよりも、Fcntl モジュールの定数を使用する方が良いでしょう。
use Fcntl ':flock'; # import LOCK_* constants # open the file for appending open (my $fh, '>>', 'test.dat') or die $!; # try to lock the file exclusively, will wait till you get the lock flock($fh, LOCK_EX); # do something with the file here (print to it in our case) # actually you should not unlock the file # close the file will unlock it close($fh) or warn "Could not close file $!";
全文をチェックしてください 群れの記録 そしてその ファイルロックのチュートリアル PerlMonks では、古いスタイルのファイル ハンドル使用法も使用されますが。
実際、私は通常、close()でエラー処理をスキップします。とにかく失敗した場合にできることはあまりないからです。
何をロックするかについては、単一のファイルで作業している場合は、そのファイルをロックします。一度に複数のファイルをロックする必要がある場合は、デッドロックを避けるために、ロックするファイルを 1 つ選択することをお勧めします。それが実際にロックする必要があるいくつかのファイルの 1 つであるか、ロック目的のためだけに作成した別のファイルであるかは、あまり関係ありません。
の使用を検討しましたか? LockFile::Simple モジュール?ほとんどの作業はすでに行われています。
私の過去の経験では、非常に使いやすく、丈夫であることがわかりました。
use strict;
use Fcntl ':flock'; # Import LOCK_* constants
# We will use this file path in error messages and function calls.
# Don't type it out more than once in your code. Use a variable.
my $file = '/path/to/some/file';
# Open the file for appending. Note the file path is in quoted
# in the error message. This helps debug situations where you
# have a stray space at the start or end of the path.
open(my $fh, '>>', $file) or die "Could not open '$file' - $!";
# Get exclusive lock (will block until it does)
flock($fh, LOCK_EX);
# Do something with the file here...
# Do NOT use flock() to unlock the file if you wrote to the
# file in the "do something" section above. This could create
# a race condition. The close() call below will unlock it
# for you, but only after writing any buffered data.
# In a world of buffered i/o, some or all of your data will not
# be written until close() completes. Always, always, ALWAYS
# check the return value on close()!
close($fh) or die "Could not write '$file' - $!";
この質問の目的は、いくつかのスクリプトのデータ ストアとして使用されているファイルをロックすることでした。最終的には、次のようなコードを使用しました (Chris から)。
open (FILE, '>>', test.dat') ; # open the file
flock FILE, 2; # try to lock the file
# do something with the file here
close(FILE); # close the file
彼の例では、close(FILE) がこのアクションも実行するので、flock FILE, 8 を削除しました。本当の問題は、スクリプトの開始時に現在のカウンターを保持し、終了時にカウンターを更新する必要があることでした。ここで Perl の問題が発生し、ファイルを読み取るには次のようにします。
open (FILE, '<', test.dat');
flock FILE, 2;
ここで結果を書き出したいのですが、ファイルを上書きしたいので、再度開いて切り詰める必要があり、その結果は次のようになります。
open (FILE, '>', test.dat'); #single arrow truncates double appends
flock FILE, 2;
この場合、実際には、ファイルが再度開かれる間の短期間、ファイルのロックが解除されます。これは、外部ロック ファイルの場合を示しています。ファイルのコンテキストを変更する場合は、ロック ファイルを使用してください。変更されたコード:
open (LOCK_FILE, '<', test.dat.lock') or die "Could not obtain lock";
flock LOCK_FILE, 2;
open (FILE, '<', test.dat') or die "Could not open file";
# read file
# ...
open (FILE, '>', test.dat') or die "Could not reopen file";
#write file
close (FILE);
close (LOCK_FILE);
から開発された http://metacpan.org/pod/File::FcntlLock
use Fcntl qw(:DEFAULT :flock :seek :Fcompat);
use File::FcntlLock;
sub acquire_lock {
my $fn = shift;
my $justPrint = shift || 0;
confess "Too many args" if defined shift;
confess "Not enough args" if !defined $justPrint;
my $rv = TRUE;
my $fh;
sysopen($fh, $fn, O_RDWR | O_CREAT) or LOGDIE "failed to open: $fn: $!";
$fh->autoflush(1);
ALWAYS "acquiring lock: $fn";
my $fs = new File::FcntlLock;
$fs->l_type( F_WRLCK );
$fs->l_whence( SEEK_SET );
$fs->l_start( 0 );
$fs->lock( $fh, F_SETLKW ) or LOGDIE "failed to get write lock: $fn:" . $fs->error;
my $num = <$fh> || 0;
return ($fh, $num);
}
sub release_lock {
my $fn = shift;
my $fh = shift;
my $num = shift;
my $justPrint = shift || 0;
seek($fh, 0, SEEK_SET) or LOGDIE "seek failed: $fn: $!";
print $fh "$num\n" or LOGDIE "write failed: $fn: $!";
truncate($fh, tell($fh)) or LOGDIE "truncate failed: $fn: $!";
my $fs = new File::FcntlLock;
$fs->l_type(F_UNLCK);
ALWAYS "releasing lock: $fn";
$fs->lock( $fh, F_SETLK ) or LOGDIE "unlock failed: $fn: " . $fs->error;
close($fh) or LOGDIE "close failed: $fn: $!";
}
ロックに代わる手段の 1 つ ファイル アプローチはロックを使用することです ソケット. 。見る ロック::ソケット このような実装については CPAN で説明します。使用方法は次のように簡単です。
use Lock::Socket qw/lock_socket/;
my $lock = lock_socket(5197); # raises exception if lock already taken
ソケットを使用すると、次のような利点があります。
- 2 つのアプリケーションが同じロックを保持しないことが (オペレーティング システムを通じて) 保証されます。競合状態はありません。
- プロセスの終了時に (やはりオペレーティング システムを通じて) クリーンアップされることが保証されているため、古いロックに対処する必要はありません。
- Perl が実行されるすべてのもので十分にサポートされている機能に依存しています。たとえば、Win32 での flock(2) サポートには問題はありません。
もちろん、明らかな欠点は、ロックの名前空間がグローバルであることです。別のプロセスが必要なポートをロックすると、一種のサービス拒否が発生する可能性があります。
[開示:私は前述のモジュールの作成者です]
flock は Unix スタイルのファイル ロックを作成し、Perl が動作するほとんどの OS で使用できます。ただし、flock のロックはアドバイスのみです。
編集:群れは持ち運び可能であることを強調した
1つのロックで読み取りと書き込みを行うための私の解決策は次のとおりです...
open (TST,"+< readwrite_test.txt") or die "Cannot open file\n$!";
flock(TST, LOCK_EX);
# Read the file:
@LINES=<TST>;
# Wipe the file:
seek(TST, 0, 0); truncate(TST, 0);
# Do something with the contents here:
push @LINES,"grappig, he!\n";
$LINES[3]="Gekke henkie!\n";
# Write the file:
foreach $l (@LINES)
{
print TST $l;
}
close(TST) or die "Cannot close file\n$!";