ファイルの排他処理

以下の文で誤解を招くような表現がありましたので、修正しました(2009.06.15)。
スクリプトで変数名に間違いがありましたので、修正しました(2009.09.27)。

3年くらい前にテストで書いた、カウンタの処理スクリプトです。2種類書いていたので掲載してみます。ファイルの排他処理はどのようにするのが正しいのかは分かりませんが、Apache Bench のオプション -n 1000 -c 10 でカウンタが壊れないことを確認しています。
stream_set_write_buffer() は効果がない、また、ファイルのアンロック(flock( $fp, LOCK_UN ))は fclose() が行うため、これらは使用していません。ftruncate() をコメントアウトしているのは、カウンタ処理では書き込みサイズが減ることはないためです。

<?php
$count_file  = '/tmp/count.txt';
$lock_object = '/tmp/lock.file';

$lock_fp = fopen( $lock_object, 'w' );
if ( flock( $lock_fp, LOCK_EX ) ) {
    $fp = fopen( $count_file, is_file( $count_file ) ? 'r+' : 'w' );
    flock( $fp, LOCK_EX );
    rewind( $fp );
    $count = (int)fgets( $fp );
    rewind( $fp );
    fwrite( $fp, ++$count );
//  ftruncate( $fp, ftell( $fp ) );
    fclose( $fp );
    fclose( $lock_fp );
}
  • mkdir() 使用版
<?php
$count_file  = '/tmp/count.txt';
$lock_object = '/tmp/lock.dir';

list( $lock, $unlock ) = get_lock_functions( $lock_object );

if ( $lock() ) {
    $fp = fopen( $count_file, is_file( $count_file ) ? 'r+' : 'w' );
    rewind( $fp );
    $count = (int)fgets( $fp );
    rewind( $fp );
    fwrite( $fp, ++$count );
//  ftruncate( $fp, ftell( $fp ) );
    fclose( $fp );
    $unlock();
}

function get_lock_functions( $lockfile, $limit = 120 )
{
    $unlockfunc = create_function( '', 'return @rmdir( "' . $lockfile . '" );' );
    $lockfunc   = create_function( '', '
        while ( ! @mkdir( "' . $lockfile . '" ) ) {
            if ( filemtime( "' . $lockfile . '" ) < time() - ' . (int)$limit . ' ) {
                @rmdir( "' . $lockfile . '" );
                return FALSE;
            }
            usleep( 100000 );   // 0.1 second
        }
        return TRUE;'
    );
    return array( $lockfunc, $unlockfunc );
}