php flock失效问题
这两天给自己的业余项目写了一个方法,用来避免crontab调度的PHP脚本并发执行。
做法
一般通过使用文件锁flock方法,令相同的PHP脚本采用非阻塞锁同一个磁盘文件,如果文件被占用则会报错,从而可以脚本立即退出。
现象
但实践中发现,在controller文件中直接flock是可以实现的,当把flock的逻辑封装到其他文件的一个函数中后就失效了。
原因
调试了半天,突然想起来以前就遇到过这个神坑。。
错误代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class Crontab { /** * 确保任务没有并发执行 */ public static function isRunning() { global $argv; $ident = []; foreach ($argv as $idx => $value) { $ident[] = $idx . '=' . urlencode($value); } $ident = md5(implode('&', $ident)); $lockDir = \Yii::getAlias('@app/runtime/crontab/'); @mkdir($lockDir, 0755, true); $file_lock = fopen($lockDir . $ident, 'w+'); $wouldBlock = 0; flock($file_lock, LOCK_EX | LOCK_NB, $wouldBlock); return $wouldBlock; } } |
根据命令行参数生成唯一hash值,代表该PHP任务。
创建锁文件,执行flock非阻塞锁,返回wouldBlock标识锁是否已被占用。
我在脚本入口调用了Crontab::isRunning()方法,发现并发启动脚本后,总是能获得锁。
错误原因是:isRunning()方法退出后,$file_lock没有继续使用,被PHP垃圾回收,$fp文件句柄关闭导致锁自动释放。
解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class Crontab { /** * 保存起来避免被php作为垃圾回收 * @var null */ static $file_lock = null; /** * 确保任务没有并发执行 */ public static function isRunning() { global $argv; $ident = []; foreach ($argv as $idx => $value) { $ident[] = $idx . '=' . urlencode($value); } $ident = md5(implode('&', $ident)); $lockDir = \Yii::getAlias('@app/runtime/crontab/'); @mkdir($lockDir, 0755, true); self::$file_lock = fopen($lockDir . $ident, 'w+'); $wouldBlock = 0; flock(self::$file_lock, LOCK_EX | LOCK_NB, $wouldBlock); return $wouldBlock; } } |
确保在整个PHP生命期内,文件句柄都不会被释放即可,所以保存在类静态成员变量里。
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~

crontab 自己也带flock -xn 也有timeout
shell命令也不错!
话说远古时代曾经用flock做过进程存活检测和心跳 >_<
可以的,flock做活锁使用简单。