我有一个php脚本,每分钟从cron运行一次。但有时它的工作时间超过1分钟。我的问题是:确保现在只有一个进程在运行的最佳方法是什么?
我使用这个代码:
$output = shell_exec('ps aux | grep some_script.php | grep -v grep'); //get all processes containing "some_script.php" and exclude current grep process
$trimmed = rtrim($output, PHP_EOL); //trim newline symbol in the end
$processes = explode(PHP_EOL, $trimmed); //get the array of lines (i.e. processes)
$procCnt = count($processes); //get number of lines
if ($procCnt > 2) {
echo "busy'n";
exit(); //exit if number of processes more than 2 (see explaination below)
}
如果有一个some_script进程从cron运行,shell_exec会返回类似的内容:
apache 13593 0.0 0.0 9228 1068 ? Ss 18:20 0:00 /bin/bash -c php -f /srv/www/robot/some_script.php 2>&1
apache 13602 0.0 0.0 290640 10544 ? S 18:20 0:00 php -f /srv/www/robot/some_script.php
因此,如果我的输出超过2行,我会调用exit()
我想问:我走的路对吗?或者有更好的方法吗?
如有任何帮助,将不胜感激
检查这种方式会创建一个竞争条件,在该条件下,两个进程可以同时检索列表,然后都决定退出。这可能是问题,也可能不是问题,这取决于你想做什么。
一个可能更好的选择是创建某种锁。我使用的一个简单的锁是一个只在进程运行时存在的目录——mkdir
是原子的,它要么成功(没有其他进程运行),要么失败(另一个进程已经创建了它)。只需确保在完成时将其删除:
if (!mkdir("lock_dir")) {
echo "busy'n";
exit();
}
register_shutdown_function(function() {
rmdir("lock_dir");
});
或者更好的是,看起来flock
是为了类似的目的而制造的。这是手册中的示例:
<?php
$fp = fopen("/tmp/lock.txt", "r+");
if (flock($fp, LOCK_EX)) { // acquire an exclusive lock
ftruncate($fp, 0); // truncate file
fwrite($fp, "Write something here'n");
fflush($fp); // flush output before releasing the lock
flock($fp, LOCK_UN); // release the lock
} else {
echo "Couldn't get the lock!";
}
fclose($fp);
?>
只需锁定脚本的运行时,类似于我的第一个示例。
最简单的方法是在进程开始时创建文件,并在进程结束时将其删除。并检查文件是否存在,然后继续创建它,否则就会死亡。
另一个是将apache的MaxClients限制为一个(如果它在您的情况下是有效的选项)。
仅针对OOP风格,我使用的是分离的Helper类。
flock便携式咨询文件锁定
register_shutdown_function—注册一个函数,以便在关闭时执行
<?php
class ProcessHelper
{
const PIDFILE = 'yourProcessName.pid';
public static function isLocked()
{
$fp = fopen(self::PIDFILE, 'w');
if (!flock($fp, LOCK_EX | LOCK_NB, $wouldBlock)) {
if ($wouldBlock) {
// if this file locked by other process
var_dump('DO NOTHING');
return true;
}
} else {
var_dump('Do something and remove PID file');
register_shutdown_function(function () {
unlink(self::PIDFILE);
});
sleep(5); //for test, uncomment
}
return false;
}
}
class FooService
{
public function init()
{
if (!ProcessHelper::isLocked()) {
var_dump('count 1000'n');
for ($i = 0; $i < 10000; $i++) {
echo $i;
}
}
}
}
$foo = new FooService();
$foo->init();