共享锁、独占锁和数据损坏


Shared locks, exclusive locks and data corruption

我的问题是:读取期间不使用共享锁是否会导致写入错误,即使写入操作使用独占锁

假设我想创建一个基于文件的计数器,如下所示:

//increment counter by 1
$fp = fopen($path, 'r+b');
if (flock($fp, LOCK_EX)) {
    //read
    fseek($fp, 0, SEEK_END);
    $size = ftell($fp);
    fseek($fp, 0, SEEK_SET);
    if ($size == 0) {
        $counter = 0;
    } else {
        $data = fread($fp, $size);
        $counter = intval($data);
    }
    //do something with data we just read
    $counter ++;
    //write
    fseek($fp, 0, SEEK_SET);
    ftruncate($fp, 0);
    fwrite($fp, $counter);
    fflush($fp);
    flock($fp, LOCK_UN);
    fclose($fp);
} else {
    fclose($fp);
    throw new Exception("Lock failed");
}

现在我想把它呈现在其他地方:

echo intval(file_get_contents($path));

请注意,file_get_contents不使用共享锁。

此代码已被证明会在页面负载过大的情况下损坏数据,即计数器多次重置为0。

我将代码更改为使用fopenLOCK_SH,目前看来还可以,但我无法确认这确实是问题的根源,因为我无法控制负载。使用多个CLIPHP实例在本地执行上述代码表明,代码甚至可以使用file_get_contents。。。

ftell手册中的注释讨论了使用附加模式打开的文件的不可预测行为。也许你可以尝试以只读方式打开文件进行读取,或者无论如何只读取文件,我不明白为什么代码中有一行可以明确地将计数器设置为0。

echo intval(false); //0

EOF的fread返回false,因此:

$fp = fopen($path, 'r+b');
if (flock($fp, LOCK_EX)) {
    //read
    $data = fread($fp, $size);
    $counter = intval($data);
    //do something with data we just read
    $counter ++;
    //write
    fseek($fp, 0, SEEK_SET);
    ftruncate($fp, 0);
    fwrite($fp, $counter);
    fflush($fp);
    flock($fp, LOCK_UN);
    fclose($fp);
} else {
    fclose($fp);
    throw new Exception("Lock failed");
}

。。。终于找到了罪魁祸首。我们的生产服务器已关闭flock功能,不幸的是,我们无法更改此功能。我想我将不得不实现自定义锁定机制。

这是由于生产服务器具有NFS文件系统,并且在大多数情况下NFS上的锁定文件根本无法工作。