检查文件是否被并发进程锁定

时间:2019-03-12 13:25:09

标签: php fopen fclose flock

我有一个使用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()是否自动解锁所有锁?

3 个答案:

答案 0 :(得分:1)

我经常使用一小类...安全且快速,基本上,只有在获得对文件的排他锁时,才必须编写,否则应该等到被锁住...

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()非常快,可以直接写入文件,但是正如您所说的那样,它有一个限制... race_condition存在,即使您尝试使用LOCK_EX也可能发生。我认为php类更加灵活和可用...

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

答案 1 :(得分:0)

第一个问题在这里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()打开的有效句柄,因此,如果句柄仍然有效,是的它将关闭文件并释放锁。

答案 2 :(得分:0)

我编写了一个使用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;
    }
}