【问题标题】:Check if file is locked by concurrent process检查文件是否被并发进程锁定
【发布时间】:2019-03-12 13:25:09
【问题描述】:

我有一个使用file_put_contents() 写入文件的进程:

file_put_contents ( $file, $data, LOCK_EX );

我添加了LOCK_EX 参数以防止并发进程写入同一文件,防止在仍在写入时尝试读取它。

由于并发性质,我很难正确测试这个,我不知道如何解决这个问题。到目前为止我已经得到了这个:

if (file_exists($file)) {
    $fp = fopen($file, 'r+');
    if (!flock($fp, LOCK_EX|LOCK_NB, $wouldblock)) {
        if ($wouldblock) {
            // how can I wait until the file is unlocked?
        } else {
            // what other reasons could there be for not being able to lock?
        }
    }
    // does calling fclose automatically close all locks even is a flock was not obtained above?
    fclose($file);
}

问题是:

  1. 有没有办法等到文件不再被锁定,同时保留给这个时间限制的选项?
  2. 如果有另一个进程锁定了文件,fclose() 是否会自动解锁所有锁?

【问题讨论】:

  • 回答您的第二个问题,不,关闭不会清除锁定。请参阅 flock 的更新日志中的第二个条目。
  • 可能你忘了flock($fp, LOCK_UN)

标签: php fopen fclose flock


【解决方案1】:

我编写了一个使用sleep() 的小测试,以便我可以通过简单的 AJAX 调用模拟并发读/写过程。这似乎回答了这两个问题:

  1. 当文件被锁定时,近似估计写入持续时间的休眠和随后的锁定检查允许等待。这甚至可以放在一个有间隔的 while 循环中。
  2. fclose() 确实从已在某些答案中确认的已运行的进程中删除锁定。

根据文档,Windows 上的 PHP5.5 及更低版本不支持 $wouldblock 参数, 我能够在 Windows + PHP5.3 上对此进行测试,并得出结论,我的测试中的 file_is_locked() 在这种情况下仍然有效: flock() 仍然会返回 false 只是没有 $wouldblock 参数,但它仍然会在我的 else 检查中被捕获。

if (isset($_POST['action'])) {
    $file = 'file.txt';
    $fp = fopen($file, 'r+');
    if ($wouldblock = file_is_locked($fp)) {
        // wait and then try again;
        sleep(5);
        $wouldblock = file_is_locked($fp);
    }
    switch ($_POST['action']) {
        case 'write':
            if ($wouldblock) {
                echo 'already writing';
            } else {
                flock($fp, LOCK_EX);
                fwrite($fp, 'yadayada');
                sleep(5);
                echo 'done writing';
            }
            break;
        case 'read':
            if ($wouldblock) {
                echo 'cant read, already writing';
            } else {
                echo fread($fp, filesize($file));
            }
            break;
    }

    fclose($fp);
    die();
}

function file_is_locked( $fp ) {
    if (!flock($fp, LOCK_EX|LOCK_NB, $wouldblock)) {
        if ($wouldblock) {
            return 'locked'; // file is locked
        } else {
            return 'no idea'; // can't lock for whatever reason (for example being locked in Windows + PHP5.3)
        }
    } else {
        return false;
    }
}

【讨论】:

    【解决方案2】:

    我经常使用一个小类……安全又快,基本上你只有在获得文件排他锁时才写,否则你应该等到被锁定……

    lock_file.php

    <?php
      /*
      Reference Material
      http://en.wikipedia.org/wiki/ACID
      */
      class Exclusive_Lock {
        /* Private variables */
        public $filename; // The file to be locked
        public $timeout = 30; // The timeout value of the lock
        public $permission = 0755; // The permission value of the locked file
        /* Constructor */
        public function __construct($filename, $timeout = 1, $permission = null, $override = false) {
          // Append '.lck' extension to filename for the locking mechanism
          $this->filename = $filename . '.lck';
          // Timeout should be some factor greater than the maximum script execution time
          $temp = @get_cfg_var('max_execution_time');
          if ($temp === false || $override === true) {
            if ($timeout >= 1) $this->timeout = $timeout;
            set_time_limit($this->timeout);
          } else {
            if ($timeout < 1) $this->timeout = $temp;
            else $this->timeout = $timeout * $temp;
          }
          // Should some other permission value be necessary
          if (isset($permission)) $this->permission = $permission;
        }
        /* Methods */
        public function acquireLock() {
          // Create the locked file, the 'x' parameter is used to detect a preexisting lock
          $fp = @fopen($this->filename, 'x');
          // If an error occurs fail lock
          if ($fp === false) return false;
          // If the permission set is unsuccessful fail lock
          if (!@chmod($this->filename, $this->permission)) return false;
          // If unable to write the timeout value fail lock
          if (false === @fwrite($fp, time() + intval($this->timeout))) return false;
          // If lock is successfully closed validate lock
          return fclose($fp);
        }
        public function releaseLock() {
          // Delete the file with the extension '.lck'
          return @unlink($this->filename);
        }
        public function timeLock() {
          // Retrieve the contents of the lock file
          $timeout = @file_get_contents($this->filename);
          // If no contents retrieved return error
          if ($timeout === false) return false;
          // Return the timeout value
          return intval($timeout);
        }
      }
    ?>
    

    简单使用如下:

      include("lock_file.php");
      $file = new Exclusive_Lock("my_file.dat", 2);
      if ($file->acquireLock()) {
        $data = fopen("my_file.dat", "w+");
        $read = "READ: YES";
        fwrite($data, $read);
        fclose($data);
        $file->releaseLock();
        chmod("my_file.dat", 0755);
        unset($data);
        unset($read);
      }
    

    如果您想添加更复杂的关卡,您可以使用另一个技巧...使用while (1) 初始化一个无限循环,该循环仅在获得独占锁时才中断,不建议这样做,因为可能会在未定义的时间内阻塞您的服务器.. .

      include("lock_file.php");
      $file = new Exclusive_Lock("my_file.dat", 2);
      while (1) {
        if ($file->acquireLock()) {
          $data = fopen("my_file.dat", "w+");
          $read = "READ: YES";
          fwrite($data, $read);
          fclose($data);
          $file->releaseLock();
          chmod("my_file.dat", 0755);
          unset($data);
          unset($read);
          break;
        }
      }
    

    file_put_contents() 速度非常快,可以直接写入文件,但正如你所说的有一个限制...... 竞争条件 存在并且即使你尝试使用LOCK_EX 也可能发生。我认为一个php类更灵活,更好用……

    看到这个处理类似问题的线程:php flock behaviour when file is locked by one process

    【讨论】:

    • 这是一个相当复杂的解决方案!如果我理解正确,这会创建一个额外的 .lck 文件,用作单独的检查机制。这不会比使用 LOCK_EX + flock 对竞争条件更敏感(因为它是更多操作)吗?类似的问题确实提供了很多信息,谢谢!
    • 很高兴为您提供帮助:)
    【解决方案3】:

    第一个问题在这里How to detect the finish with file_put_contents() in php? 得到解答,因为 PHP 是单线程的,唯一的解决方案是使用 PTHREADS 对核心 PHP 进行扩展,关于它的一篇很好的简单文章是 https://www.mullie.eu/parallel-processing-multi-tasking-php/

    这里回答第二个问题Will flock'ed file be unlocked when the process die unexpectedly?

    fclose() 将仅解锁使用 fopen() 或 fsockopen() 打开的有效句柄,因此如果句柄仍然有效,是的,它将关闭文件并释放锁定。

    【讨论】:

    • 如果我理解正确的话,关于检测file_put_contents() 完成链接到的答案是关于单个请求。但我可以有一个并行请求(甚至是同一个 PHP 文件)尝试读取或写入同一个文件。除非我误解,这不涉及 PTHREADS?
    【解决方案4】:

    这是@Alessandro 答案的修复,它可以正常工作并且不会永远锁定文件 lock_file.php

        <?php
      /*
      Reference Material
      http://en.wikipedia.org/wiki/ACID
      */
      class Exclusive_Lock {
        /* Private variables */
        public $filename; // The file to be locked
        public $timeout = 30; // The timeout value of the lock
        public $permission = 0755; // The permission value of the locked file
        /* Constructor */
        public function __construct($filename, $timeout = 1, $permission = null, $override = false) {
          // Append '.lck' extension to filename for the locking mechanism
          $this->filename = $filename . '.lck';
          // Timeout should be some factor greater than the maximum script execution time
          $temp = @get_cfg_var('max_execution_time');
          if ($temp === false || $override === true) {
            if ($timeout >= 1) $this->timeout = $timeout;
            set_time_limit($this->timeout);
          } else {
            if ($timeout < 1) $this->timeout = $temp;
            else $this->timeout = $timeout ;
          }
          
          // Should some other permission value be necessary
          if (isset($permission)) $this->permission = $permission;
          if($this->timeLock()){
              $this->releaseLock();
          }
    
        }
        /* Methods */
        public function acquireLock() {
          // Create the locked file, the 'x' parameter is used to detect a preexisting lock
          $fp = @fopen($this->filename, 'x');
          // If an error occurs fail lock
          if ($fp === false) return false;
          // If the permission set is unsuccessful fail lock
          if (!@chmod($this->filename, $this->permission)) return false;
          // If unable to write the timeout value fail lock
          if (false === @fwrite($fp, time() + intval($this->timeout))) return false;
          // If lock is successfully closed validate lock
          return fclose($fp);
        }
        public function releaseLock() {
          // Delete the file with the extension '.lck'
          return @unlink($this->filename);
        }
        private function timeLock() {
          // Retrieve the contents of the lock file
          $timeout = @file_get_contents($this->filename);
          // If no contents retrieved return true
          if ($timeout === false) return true;
          // Return the timeout value
          return (intval($timeout) < time());
        }
      }
    

    如下使用:

      include("lock_file.php");
      $file = new Exclusive_Lock("my_file.dat", 2);
      if ($file->acquireLock()) {
        $data = fopen("my_file.dat", "w+");
        $read = "READ: YES";
        fwrite($data, $read);
        fclose($data);
        $file->releaseLock();
        chmod("my_file.dat", 0755);
        unset($data);
        unset($read);
      }
    

    希望能节省一些时间

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-15
      相关资源
      最近更新 更多