PHP的flock()文档页面指出,在IIS下使用它是不安全的。如果我不能在所有情况下都依赖flock,有没有其他方法可以安全地实现同样的目标?
发布于 2011-08-06 23:30:43
在所有假想的可能情况下,没有其他选择可以安全地实现同样的目标。这是通过设计计算机系统和the job is not trivial for cross-platform code实现的。
如果您需要安全地使用flock(),请记录您的应用程序的要求。
或者,您可以创建自己的锁定机制,但是您必须确保它是原子的。这意味着,您必须测试锁,如果它不存在,则建立锁,同时您需要确保其他任何人都无法获得中间的锁。
这可以通过创建一个表示锁的锁文件来实现,但前提是锁文件不存在。不幸的是,PHP没有提供这样的函数来以这种方式创建文件。
或者,您可以使用mkdir()创建一个目录并处理结果,因为在创建该目录时,它将返回true;如果该目录已经存在,它将返回false。
发布于 2014-03-19 23:49:53
您可以在基于mkdir的读/写操作周围实现一个文件锁-解锁模式,因为它是原子的,而且速度非常快。我已经对此进行了压力测试,与mgutt不同的是,我没有发现瓶颈。不过,您必须处理死锁情况,这可能就是mgutt所经历的情况。死锁是指两个锁尝试相互等待。它可以通过锁定尝试的随机间隔来补救。如下所示:
// call this always before reading or writing to your filepath in concurrent situations
function lockFile($filepath){
clearstatcache();
$lockname=$filepath.".lock";
// if the lock already exists, get its age:
$life=@filectime($lockname);
// attempt to lock, this is the really important atomic action:
while (!@mkdir($lockname)){
if ($life)
if ((time()-$life)>120){
//release old locks
rmdir($lockname);
$life=false;
}
usleep(rand(50000,200000));//wait random time before trying again
}
}然后在filepath中处理您的文件,完成后,调用:
function unlockFile($filepath){
$unlockname= $filepath.".lock";
return @rmdir($unlockname);
}我选择删除旧的锁,直到PHP执行时间达到最大值,以防脚本在解锁之前退出。一种更好的方法是在脚本失败时始终删除锁。有一个很好的方法,但是我忘了。
发布于 2011-10-28 17:48:15
我的建议是使用mkdir()而不是flock()。这是一个读/写缓存的真实示例,展示了不同之处:
$data = false;
$cache_file = 'cache/first_last123.inc';
$lock_dir = 'cache/first_last123_lock';
// read data from cache if no writing process is running
if (!file_exists($lock_dir)) {
// we suppress error messages as the cache file exists in 99,999% of all requests
$data = @include $cache_file;
}
// cache file not found
if ($data === false) {
// get data from database
$data = mysqli_fetch_assoc(mysqli_query($link, "SELECT first, last FROM users WHERE id = 123"));
// write data to cache if no writing process is running (race condition safe)
// we suppress E_WARNING of mkdir() because it is possible in 0,001% of all requests that the dir already exists after calling file_exists()
if (!file_exists($lock_dir) && @mkdir($lock_dir)) {
file_put_contents($cache_file, '<?php return ' . var_export($data, true) . '; ?' . '>')) {
// remove lock
rmdir($lock_dir);
}
}现在,我们尝试使用flock()来实现同样的目标
$data = false;
$cache_file = 'cache/first_last123.inc';
// we suppress error messages as the cache file exists in 99,999% of all requests
$fp = @fopen($cache_file, "r");
// read data from cache if no writing process is running
if ($fp !== false && flock($fp, LOCK_EX | LOCK_NB)) {
// we suppress error messages as the cache file exists in 99,999% of all requests
$data = @include $cache_file;
flock($fp, LOCK_UN);
}
// cache file not found
if (!is_array($data)) {
// get data from database
$data = mysqli_fetch_assoc(mysqli_query($link, "SELECT first, last FROM users WHERE id = 123"));
// write data to cache if no writing process is running (race condition safe)
$fp = fopen($cache_file, "c");
if (flock($fp, LOCK_EX | LOCK_NB)) {
ftruncate($fp, 0);
fwrite($fp, '<?php return ' . var_export($data, true) . '; ?' . '>');
flock($fp, LOCK_UN);
}
}重要的部分是LOCK_NB,以避免阻塞所有连续的请求:
如果您不希望LOCK_NB ()在锁定时阻塞,也可以将flock作为位掩码添加到上述操作之一。
如果没有它,代码将会产生巨大的瓶颈!
另外一个重要的部分是if (!is_array($data)) {。这是因为$data可能包含:
array()作为失败includefalse的结果为空字符串(race condition)如果第一个访问者执行下面这行代码,就会出现竞争条件:
$fp = fopen($cache_file, "c");而另一个访问者在一毫秒后执行这一行:
if ($fp !== false && flock($fp, LOCK_EX | LOCK_NB)) {这意味着第一个访问者创建了空文件,而第二个访问者创建了锁,因此include返回一个空字符串。
因此,您看到了许多通过使用mkdir()可以避免的陷阱,而且速度也快了7倍:
$filename = 'index.html';
$loops = 10000;
$start = microtime(true);
for ($i = 0; $i < $loops; $i++) {
file_exists($filename);
}
echo __LINE__ . ': ' . round(microtime(true) - $start, 5) . PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < $loops; $i++) {
$fp = @fopen($filename, "r");
flock($fp, LOCK_EX | LOCK_NB);
}
echo __LINE__ . ': ' . round(microtime(true) - $start, 5) . PHP_EOL;结果:
file_exists: 0.00949
fopen/flock: 0.06401另外,正如你所看到的,我在mkdir()前面使用了file_exists()。这是因为my tests (德语)单独使用mkdir()导致了瓶颈。
https://stackoverflow.com/questions/6967553
复制相似问题