检查文件是否被并发进程锁定
Check if file is locked by concurrent process
我有一个使用 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);
}
问题是:
- 有没有办法等到文件不再被锁定,同时保留给这个时间限制的选项?
- 当有另一个进程锁定该文件时,
fclose()
是否会自动解锁所有锁?
这里回答了第一个问题How to detect the finish with file_put_contents() in php? and beacuse PHP is single-threaded, only solution is to use extension of core PHP using PTHREADS and one good simple article about it is https://www.mullie.eu/parallel-processing-multi-tasking-php/
这里回答第二个问题Will flock'ed file be unlocked when the process die unexpectedly?
fclose() 将仅解锁使用 fopen() 或 fsockopen() 打开的有效句柄,因此如果句柄仍然有效,它将关闭文件并释放锁。
我经常使用一个小的class...它安全且快速,基本上只有当您获得文件的独占锁时才必须写入,否则您应该等到被锁定...
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 class 更灵活和可用...
看到这个处理类似问题的线程:php flock behaviour when file is locked by one process
我写了一个使用 sleep()
的小测试,这样我就可以通过一个简单的 AJAX 调用来模拟并发 read/write 进程。这似乎回答了两个问题:
- 当文件被锁定时,接近估计写入持续时间的休眠和随后的锁定检查允许等待。这甚至可以放在一个有间隔的 while 循环中。
fclose()
确实 没有 从已经 运行 的进程中删除锁,正如一些答案所确认的那样。
根据文档,PHP5.5 和 windows 下的版本不支持 $wouldblock
参数,
我能够在 Windows + PHP5.3 上对此进行测试,并得出结论,我测试的 file_is_locked()
在这种情况下仍然有效:
flock()
仍然 return 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;
}
}
这是@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);
}
希望节省一些时间
我有一个使用 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);
}
问题是:
- 有没有办法等到文件不再被锁定,同时保留给这个时间限制的选项?
- 当有另一个进程锁定该文件时,
fclose()
是否会自动解锁所有锁?
这里回答了第一个问题How to detect the finish with file_put_contents() in php? and beacuse PHP is single-threaded, only solution is to use extension of core PHP using PTHREADS and one good simple article about it is https://www.mullie.eu/parallel-processing-multi-tasking-php/
这里回答第二个问题Will flock'ed file be unlocked when the process die unexpectedly?
fclose() 将仅解锁使用 fopen() 或 fsockopen() 打开的有效句柄,因此如果句柄仍然有效,它将关闭文件并释放锁。
我经常使用一个小的class...它安全且快速,基本上只有当您获得文件的独占锁时才必须写入,否则您应该等到被锁定...
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 class 更灵活和可用...
看到这个处理类似问题的线程:php flock behaviour when file is locked by one process
我写了一个使用 sleep()
的小测试,这样我就可以通过一个简单的 AJAX 调用来模拟并发 read/write 进程。这似乎回答了两个问题:
- 当文件被锁定时,接近估计写入持续时间的休眠和随后的锁定检查允许等待。这甚至可以放在一个有间隔的 while 循环中。
fclose()
确实 没有 从已经 运行 的进程中删除锁,正如一些答案所确认的那样。
PHP5.5 和 windows 下的版本不支持 $wouldblock
参数,
我能够在 Windows + PHP5.3 上对此进行测试,并得出结论,我测试的 file_is_locked()
在这种情况下仍然有效:
flock()
仍然 return 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;
}
}
这是@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);
}
希望节省一些时间