首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >PHP flock()替代方法

PHP flock()替代方法
EN

Stack Overflow用户
提问于 2011-08-06 22:43:54
回答 7查看 7.8K关注 0票数 14

PHP的flock()文档页面指出,在IIS下使用它是不安全的。如果我不能在所有情况下都依赖flock,有没有其他方法可以安全地实现同样的目标?

EN

回答 7

Stack Overflow用户

回答已采纳

发布于 2011-08-06 23:30:43

在所有假想的可能情况下,没有其他选择可以安全地实现同样的目标。这是通过设计计算机系统和the job is not trivial for cross-platform code实现的。

如果您需要安全地使用flock(),请记录您的应用程序的要求。

或者,您可以创建自己的锁定机制,但是您必须确保它是原子的。这意味着,您必须测试锁,如果它不存在,则建立锁,同时您需要确保其他任何人都无法获得中间的锁。

这可以通过创建一个表示锁的锁文件来实现,但前提是锁文件不存在。不幸的是,PHP没有提供这样的函数来以这种方式创建文件。

或者,您可以使用mkdir()创建一个目录并处理结果,因为在创建该目录时,它将返回true;如果该目录已经存在,它将返回false

票数 8
EN

Stack Overflow用户

发布于 2014-03-19 23:49:53

您可以在基于mkdir的读/写操作周围实现一个文件锁-解锁模式,因为它是原子的,而且速度非常快。我已经对此进行了压力测试,与mgutt不同的是,我没有发现瓶颈。不过,您必须处理死锁情况,这可能就是mgutt所经历的情况。死锁是指两个锁尝试相互等待。它可以通过锁定尝试的随机间隔来补救。如下所示:

代码语言:javascript
复制
// 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中处理您的文件,完成后,调用:

代码语言:javascript
复制
function unlockFile($filepath){
   $unlockname= $filepath.".lock";   
   return @rmdir($unlockname);
}

我选择删除旧的锁,直到PHP执行时间达到最大值,以防脚本在解锁之前退出。一种更好的方法是在脚本失败时始终删除锁。有一个很好的方法,但是我忘了。

票数 3
EN

Stack Overflow用户

发布于 2011-10-28 17:48:15

我的建议是使用mkdir()而不是flock()。这是一个读/写缓存的真实示例,展示了不同之处:

代码语言:javascript
复制
$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()来实现同样的目标

代码语言:javascript
复制
$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可能包含:

  1. array()作为失败include
  2. or的db query
  3. false的结果为空字符串(race condition)

如果第一个访问者执行下面这行代码,就会出现竞争条件:

代码语言:javascript
复制
$fp = fopen($cache_file, "c");

而另一个访问者在一毫秒后执行这一行:

代码语言:javascript
复制
if ($fp !== false && flock($fp, LOCK_EX | LOCK_NB)) {

这意味着第一个访问者创建了空文件,而第二个访问者创建了锁,因此include返回一个空字符串。

因此,您看到了许多通过使用mkdir()可以避免的陷阱,而且速度也快了7倍:

代码语言:javascript
复制
$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;

结果:

代码语言:javascript
复制
file_exists: 0.00949
fopen/flock: 0.06401

另外,正如你所看到的,我在mkdir()前面使用了file_exists()。这是因为my tests (德语)单独使用mkdir()导致了瓶颈。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/6967553

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档