PHP 实现进程锁与多进程控制:防止定时任务重叠运行
在使用 Linux 定时任务(Crontab)运行 PHP 脚本时,若程序处理耗时超过调度间隔,容易引发同一任务的多个实例同时运行,造成资源浪费甚至数据错误。通过引入进程锁机制,可以有效控制并发的脚本实例数量。
为什么需要进程锁
进程锁的核心目的是防止重复执行。当定时任务每分钟触发一次,而上次执行的进程尚未结束时,新的进程会立即生成。加上锁判断后,新进程会检测锁文件是否存在,若存在则放弃执行,从而保证只有一个实例在运行。
准备条件
- Linux 环境:推荐使用 Shell 命令和后台运行。
- 文件写入权限:确保运行 PHP 的用户对指定目录有写权限,以便创建锁文件。
- PHP 基础扩展:主要依赖
flock函数,这是标准库函数,无需额外安装。
第1步:实现单进程的固定锁
最简单的方式是为整个脚本创建一个固定的锁文件。每次执行时尝试获取独占锁,获取失败说明已有进程在运行。
新建 process.php,代码如下:
<?php
$lock_file = dirname(__FILE__) . "/process.lock";
$lock_file_handle = fopen($lock_file, 'w');
if ($lock_file_handle === false) {
die("Can not create lock file {$lock_file}\n");
}
// LOCK_EX + LOCK_NB:以独占方式加锁,且不阻塞(无法获取立刻报错)
if (!flock($lock_file_handle, LOCK_EX + LOCK_NB)) {
die(date("Y-m-d H:i:s") . " Process already exists.\n");
}
while(1){
sleep(1); // 模拟长时间运行,测试时请按需调整
}
在命令行使用 & 符号在后台启动该脚本:
php process.php &
第一次执行会正常运行,若在另一个终端再次运行同一脚本,则会输出:Process already exists.,表示被锁拒止。这种实现适合只有一个固定入口的脚本。
第2步:进程锁实现多进程
实际场景中,可能需要允许不同业务逻辑(如不同的用户ID或任务类型)并行执行。可以通过脚本参数(Argv)来生成不同的锁文件名,实现“一类一锁”的效果。
改进后的代码如下:
if(!($argc > 1)){
// 无参数:默认使用全局锁
$lock_file = dirname(__FILE__) . "/process.lock";
}else{
// 有参数:根据参数组合生成锁名
unset($argv[0]); // 移除脚本自身名称
$lock_file = dirname(__FILE__) . "/process". implode('.', $argv) .".lock";
}
$lock_file_handle = fopen($lock_file, 'w');
if ($lock_file_handle === false){
die("Can not create lock file $lock_file\n");
}
if (!flock($lock_file_handle, LOCK_EX + LOCK_NB)) {
die(date("Y-m-d H:i:s") . " Process already exists.\n");
}
while(1){
sleep(1);
}
效果验证:
- 执行
php process.php 1成功后,再执行php process.php 1会被拦截。 - 此时执行
php process.php 2将成功运行,因为锁文件变为process.2.lock,互不影响。 - 传入多参数如
php process.php 1 2也会生成特定的process.1.2.lock进行隔离。
第3步:在框架(如 Yii2)中的集成
在实际项目中,通常使用框架(如 Yii2)。框架的控制器的参数传递更加规范,原理相同。
核心步骤:
- 继承 Controller:确保你的类继承自
yii\console\Controller,以便正确处理控制台参数。 - 添加逻辑:在 action 方法的头部嵌入上述的第2步代码。
- Crontab 调用:通过控制台命令格式调用。例如:
* * * * * root /usr/local/bin/php /data/www/html/newVanish/yii 控制器名/方法 传递的参数 > /dev/null 2>&1
这样既利用了框架的便利性,又获得了底层的并发保护。
第4步:强制清理僵死进程(风险提醒)
如果在测试过程中卡死,或者由于非正常退出导致锁文件残留但进程已消失,可以手动终止对应的 PHP 进程。
以下命令可根据进程特征批量杀除,请谨慎操作:
ps aux|grep process_name |grep -v grep|awk '{print $2}'|xargs kill -9
注意:请将 process_name 替换为实际的进程标识字符串。该命令直接终止进程号,可能导致数据未保存,使用前建议确认进程状态。
完成判断与总结
通过检查 die() 的输出或进程列表(ps),即可确认锁是否生效。以上就是 PHP 中实现进程锁与多进程控制的详细内容,更多关于 PHP 优化与并发处理的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









