确保Linux上只有一个正在运行的PHP进程

时间:2013-12-11 16:48:38

标签: php linux cron

我每分钟都有一个从cron运行的php脚本。但有时它的工作时间超过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

所以如果输出中有两行以上,我会调用exit()

我想问:我是否正确?或者有更好的方法吗?

任何帮助将不胜感激

3 个答案:

答案 0 :(得分:4)

检查这种方式会创建一个竞争条件,其中两个进程可以同时检索列表,然后两者都决定退出。根据您要做的事情,这可能是也可能不是问题。

一种可能更好的选择是创建某种锁。我使用的一个简单的例子是一个只在进程运行时才存在的目录 - 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);

?>

只需保持脚本运行时的锁定,类似于我的第一个示例。

答案 1 :(得分:1)

最简单的方法是在进程启动时创建文件,并在最后删除它。 并检查文件是否存在,然后继续创建或死亡。

另一个是将apache的MaxClients限制为一个(如果在你的情况下它是有效的选项)。

答案 2 :(得分:0)

仅针对OOP风格,我使用的是分离的Helper类。

flock — Portable advisory file locking

register_shutdown_function — Register a function for execution on shutdown

<?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();