如何检测PHP脚本是否已在运行?

时间:2010-11-10 01:50:02

标签: php shell

我有一个cron脚本,每10分钟执行一次PHP脚本。该脚本检查队列并处理队列中的数据。有时队列有足够的数据可以持续超过10分钟的处理,从而产生了两个脚本试图访问相同数据的可能性。我希望能够检测脚本是否已经运行以防止启动脚本的多个副本。我想过创建一个数据库标志,说明脚本正在处理,但如果脚本崩溃,它会使它处于正状态。有没有一种简单的方法可以判断PHP脚本是否已经使用PHP或shell脚本运行?

11 个答案:

答案 0 :(得分:32)

您可以使用锁定文件。 PHP的flock()函数为Unix的flock函数提供了一个简单的包装器,它提供了对文件的建议锁。

如果您没有明确释放它们,操作系统会在持有它们的进程终止时自动释放这些锁,即使它终止异常。

您还可以遵循松散的Unix惯例,即将您的锁定文件设置为PID文件' - 也就是说,在获取文件锁定后,让脚本将PID写入其中。即使你从未在脚本中读过这个内容,如果你的脚本挂起或发疯并想要找到它的PID以便手动杀死它,它将会很方便。

这是一个复制/粘贴就绪的实现:

#!/usr/bin/php
<?php

$lock_file = fopen('path/to/yourlock.pid', 'c');
$got_lock = flock($lock_file, LOCK_EX | LOCK_NB, $wouldblock);
if ($lock_file === false || (!$got_lock && !$wouldblock)) {
    throw new Exception(
        "Unexpected error opening or locking lock file. Perhaps you " .
        "don't  have permission to write to the lock file or its " .
        "containing directory?"
    );
}
else if (!$got_lock && $wouldblock) {
    exit("Another instance is already running; terminating.\n");
}

// Lock acquired; let's write our PID to the lock file for the convenience
// of humans who may wish to terminate the script.
ftruncate($lock_file, 0);
fwrite($lock_file, getmypid() . "\n");

/*
    The main body of your script goes here.
*/
echo "Hello, world!";

// All done; we blank the PID file and explicitly release the lock 
// (although this should be unnecessary) before terminating.
ftruncate($lock_file, 0);
flock($lock_file, LOCK_UN);

只需将锁定文件的路径设置为您喜欢的位置即可。

答案 1 :(得分:6)

如果你需要它绝对防崩溃,你应该使用semaphores,当php结束特定的请求处理时会自动释放它。

更简单的方法是在执行开始时创建数据库记录或文件,并在结尾处删除它。您可以随时检查该记录/文件的“年龄”,如果它比正常脚本执行的时间早3倍,则假设它已崩溃并将其删除。

没有“银弹”,它只取决于你的需求。

答案 2 :(得分:5)

如果您正在运行Linux,则应该在脚本的顶部工作:

$running = exec("ps aux|grep ". basename(__FILE__) ."|grep -v grep|wc -l");
if($running > 1) {
   exit;
}

答案 3 :(得分:3)

* nix守护进程的常用方法(虽然不一定是PHP脚本,但它可以工作)是使用.pid文件。

当脚本开始检查是否存在为脚本命名的.pid文件(通常存储在/ var / run /中)。如果它不存在,则创建它将其内容设置为运行脚本的进程的PID(使用getmypid),然后继续正常执行。如果它确实存在,则从中读取PID并查看该进程是否仍在运行,可能是通过运行ps $pid。如果它正在运行,请退出。否则,用PID覆盖其内容(如上所述)并继续正常执行。

执行完毕后,删除文件。

答案 4 :(得分:3)

我知道这是一个老问题但是如果其他人在这里看,我会发布一些代码。这是我最近在类似的情况下所做的,而且效果很好。把这段代码放在你文件的顶部,如果同一个脚本已经运行,它将保留并结束新的。

我用它来保持监控系统始终运行。一个cron作业每5分钟启动一次脚本,但是除非另一个因为某种原因停止了(通常如果它已经崩溃了,这是非常罕见的!)新的就会退出。

// The file to store our process file
define('PROCESS_FILE', 'process.pid');

// Check I am running from the command line
if (PHP_SAPI != 'cli') {
    log_message('Run me from the command line');
    exit;
}

// Check if I'm already running and kill myself off if I am
$pid_running = false;
if (file_exists(PROCESS_FILE)) {
    $data = file(PROCESS_FILE);
    foreach ($data as $pid) {
        $pid = (int)$pid;
        if ($pid > 0 && file_exists('/proc/' . $pid)) {
            $pid_running = $pid;
            break;
        }
    }
}
if ($pid_running && $pid_running != getmypid()) {
    if (file_exists(PROCESS_FILE)) {
        file_put_contents(PROCESS_FILE, $pid);
    }
    log_message('I am already running as pid ' . $pid . ' so stopping now');
    exit;
} else {
    // Make sure file has just me in it
    file_put_contents(PROCESS_FILE, getmypid());
    log_message('Written pid with id '.getmypid());
}

如果没有在Windows上进行修改,它将无法工作,但在基于unix的系统中应该没问题。

答案 5 :(得分:3)

您可以使用新的Symfony 2.6 LockHandlerSource

$lock = new LockHandler('update:contents');
if (!$lock->lock()) {
    echo 'The command is already running in another process.';
}

答案 6 :(得分:0)

这对我有用。使用锁定标志和时间戳设置数据库记录。我的脚本应该在15分钟内完成,所以添加了作为最后一个锁定的领域来检查:

       $lockresult = mysql_query("
       SELECT *
       FROM queue_locks
       WHERE `lastlocked` > DATE_SUB(NOW() , INTERVAL 15 MINUTE)    
       AND `locked` = 'yes'
       AND `queid` = '1'
       LIMIT 1
        "); 
$LockedRowCount = mysql_num_rows($lockresult);

if($LockedRowCount>0){
    echo "this script is locked, try again later";
    exit;   
}else{
//Set the DB record to locked and carry on son
$result = mysql_query("
            UPDATE `queue_locks` SET `locked` = 'yes', `lastlocked` = CURRENT_TIMESTAMP WHERE `queid` = 1;
            ");

}

然后在脚本末尾解锁它:

    $result = mysql_query("UPDATE `queue_locks` SET `locked` = 'no' WHERE `queid` = 1;");

答案 7 :(得分:0)

首页 / 检查 PHP 脚本是否已在运行

检查 PHP 脚本是否已经在运行 如果您使用由 cron 运行的 PHP 长时间运行批处理,并且您想确保只有一个正在运行的脚本副本,您可以使用函数 getmypid() 和 posix_kill() 来检查您是否已经有一个正在运行的进程的副本。这篇文章有一个 PHP 类,用于检查脚本是否已经在运行。

在 Linux/Unix 计算机上运行的每个进程都有一个 pid 或进程标识符。在 PHP 中,这可以使用 getmypid() 检索,它将返回一个整数。这个pid号可以保存到一个文件中,每次运行脚本时都会检查文件是否存在。如果是,则可以使用 posix_kill() 函数查看进程是否正在使用该 pid 编号运行。

我用于执行此操作的 PHP 类如下。请随意使用并修改以满足您的个人需求。

class pid {

    protected $filename;
    public $already_running = false;
   
    function __construct($directory) {
       
        $this->filename = $directory . '/' . basename($_SERVER['PHP_SELF']) . '.pid';
       
        if(is_writable($this->filename) || is_writable($directory)) {
           
            if(file_exists($this->filename)) {
                $pid = (int)trim(file_get_contents($this->filename));
                if(posix_kill($pid, 0)) {
                    $this->already_running = true;
                }
            }
           
        }
        else {
            die("Cannot write to pid file '$this->filename'. Program execution halted.n");
        }
       
        if(!$this->already_running) {
            $pid = getmypid();
            file_put_contents($this->filename, $pid);
        }
       
    }

    public function __destruct() {

        if(!$this->already_running && file_exists($this->filename) && is_writeable($this->filename)) {
            unlink($this->filename);
        }
   
    }
   
}

使用下面的类

$pid = new pid('/tmp');
if($pid->already_running) {
    echo "Already running.n";
    exit;
}
else {
    echo "Running...n";
}

答案 8 :(得分:-1)

我知道这是一个老问题,但我认为值得考虑之前有一种前所未有的方法。

如前所述,锁文件或数据库标志解决方案的一个问题是,如果脚本因正常完成之外的某些原因而失败,则不会释放锁。因此,在手动清除锁定或通过清理功能清除锁定之前,下一个实例不会启动。

但是,如果您确定脚本应该只运行一次,那么从脚本中检查它是否已经在运行时相对容易。这是一些代码:

function checkrun() {
    exec("ps auxww",$ps);
    $r = 0;
    foreach ($ps as $p) {
        if (strpos($p,basename(__FILE__))) {
            $r++;
            if ($r > 1) {
                echo "too many instances, exiting\n";
                exit();
            }
        }
    }
}

只需在脚本开始时调用此函数,然后再执行任何其他操作(例如打开数据库处理程序或处理导入文件),如果相同的脚本已在运行,则它将在进程列表中出现两次 - 一次用于前一个实例,一次用于此实例。因此,如果它出现不止一次,请退出。

这里有一个潜在的问题:我假设您永远不会有两个具有相同基本名称的脚本可以同时合法运行(例如,在两个不同用户下运行的相同脚本)。如果这是可能的话,那么你需要将检查扩展到比文件基本名称上的简单子字符串更复杂的东西。但是,如果您的脚本具有唯一的文件名,那么这种方法就足够了。

答案 9 :(得分:-1)

我试过这个。在Windows中运行良好。

    $status_file=__DIR__.'/mail_process.txt';
    if(file_exists($status_file) && file_get_contents($status_file)=='running'){
                echo 'Program running';
            exit;
    }
    file_put_contents($status_file,'running');

       // Code block
       //...........

    file_put_contents($status_file,'finished');

感谢@martinodf的想法。

答案 10 :(得分:-1)

假设这是一台linux服务器,并且您有可用的cronjobs

///Check for running script and run if non-exist///

#! /bin/bash

check=$(ps -fea | grep -v grep | grep script.php | wc -l)
date=$(date +%Y-%m%d" "%H:%M:%S)
if [ "$check" -lt 1 ]; then
echo "["$date"] Starting script" >> /path/to/script/log/
/sbin/script  ///Call the script here - see below///
fi

脚本文件

#/usr/bin/php /path/to/your/php/script.php