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

时间:2022-07-11 16:25:12

I have a cron script that executes a PHP script every 10 minutes. The script checks a queue and processes the data in the queue. Sometimes the queue has enough data to last over 10 minutes of processing, creating the potential of two scripts trying to access the same data. I want to be able to detect whether the script is already running to prevent launching multiple copies of the script. I thought about creating a database flag that says that a script is processing, but if the script were ever to crash it would leave it in the positive state. Is there an easy way to tell if the PHP script is already running from withing a PHP or shell script?

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

9 个解决方案

#1


5  

If you need it to be absolutely crash-proof, you should use semaphores, which are released automatically when php ends the specific request handling.

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

A simpler approach would be to create a DB record or a file at the beginning of the execution, and remove it at the end. You could always check the "age" of that record/file, and if it's older than say 3 times the normal script execution, suppose it crashed and remove it.

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

There's no "silver bullet", it just depends on your needs.

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

#2


25  

You can just use a lock file. PHP's flock() function provides a simple wrapper for Unix's flock function, which provides advisory locks on files.

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

If you don't explicitly release them, the OS will automatically release these locks for you when the process holding them terminates, even if it terminates abnormally.

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

You can also follow the loose Unix convention of making your lock file a 'PID file' - that is, upon obtaining a lock on the file, have your script write its PID to it. Even if you never read this from within your script, it will be convenient for you if your script ever hangs or goes crazy and you want to find its PID in order to manually kill it.

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

Here's a copy/paste-ready implementation:

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

#!/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);

Just set the path of your lock file to wherever you like and you're set.

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

#3


4  

If you are running Linux, this should work at the top of your script:

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

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

#4


3  

A common way for *nix daemons (though not necessarily PHP scripts, but it will work) is to use a .pid file.

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

When the script starts check for the existence of a .pid file named for the script (generally stored in /var/run/). If it doesn't exist, create it setting its contents to the PID of the process running the script (using getmypid) then continue with normal execution. If it does exist read the PID from it and see if that process is still running, probably by running ps $pid. If it is running, exit. Otherwise, overwrite its contents with your PID (as above) and continue normal execution.

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

When execution finished, delete the file.

执行完成后,删除该文件。

#5


3  

I know this is an old question but in case someone else is looking here I'll post some code. This is what I have done recently in a similar situation and it works well. Put put this code at the top of your file and if the same script is already running it will leave it be and end the new one.

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

I use it to keep a monitoring system running at all times. A cron job starts the script every 5 minutes but unless the other has stopped from some reason (usually if it has crashed, which is very rare!) the new one will just exit itself.

我用它来保持监控系统始终运行。一个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());
}

It will NOT work without modification on Windows, but should be fine in unix based systems.

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

#6


3  

You can use new Symfony 2.6 LockHandler

您可以使用新的Symfony 2.6 LockHandler

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

#7


0  

This worked for me. Set a database record with a lock flag and a time stamp. My script should complete well within 15min so added that as a last locked feild to check:

这对我有用。使用锁定标志和时间戳设置数据库记录。我的脚本应该在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;
            ");

}

Then unlock it at the end of the script:

然后在脚本的末尾解锁它:

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

#8


0  

I have tried this. Works well in Windows too.

我试过这个。在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');

Thanks to @martinodf for his idea.

感谢@martinodf的想法。

#9


-1  

I know this is an old question, but there's an approach which hasn't been mentioned before that I think is worth considering.

我知道这是一个老问题,但有一种方法在我认为值得考虑之前没有提到过。

One of the problems with a lockfile or database flag solution, as already mentioned, is that if the script fails for some reason other than normal completion it won't release the lock. And therefore the next instance won't start until the lock is either manually cleared or cleared by a clean-up function.

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

If, though, you are certain that the script should only ever be running once, then it's relatively easy to check from within the script whether it is already running when you start it. Here's some code:

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

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

Simply call this function at the start of the script, before you do anything else (such as open a database handler or process an import file), and if the same script is already running then it will appear twice in the process list - once for the previous instance, and once for this one. So, if it appears more than once, just exit.

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

A potential gotcha here: I'm assuming that you will never have two scripts with the same basename that may legitimately run simultaneously (eg, the same script running under two different users). If that is a possibility, then you'd need to extend the checking to something more sophisticated than a simple substring on the file's basename. But this works well enough if you have unique filenames for your scripts.

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

#1


5  

If you need it to be absolutely crash-proof, you should use semaphores, which are released automatically when php ends the specific request handling.

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

A simpler approach would be to create a DB record or a file at the beginning of the execution, and remove it at the end. You could always check the "age" of that record/file, and if it's older than say 3 times the normal script execution, suppose it crashed and remove it.

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

There's no "silver bullet", it just depends on your needs.

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

#2


25  

You can just use a lock file. PHP's flock() function provides a simple wrapper for Unix's flock function, which provides advisory locks on files.

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

If you don't explicitly release them, the OS will automatically release these locks for you when the process holding them terminates, even if it terminates abnormally.

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

You can also follow the loose Unix convention of making your lock file a 'PID file' - that is, upon obtaining a lock on the file, have your script write its PID to it. Even if you never read this from within your script, it will be convenient for you if your script ever hangs or goes crazy and you want to find its PID in order to manually kill it.

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

Here's a copy/paste-ready implementation:

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

#!/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);

Just set the path of your lock file to wherever you like and you're set.

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

#3


4  

If you are running Linux, this should work at the top of your script:

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

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

#4


3  

A common way for *nix daemons (though not necessarily PHP scripts, but it will work) is to use a .pid file.

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

When the script starts check for the existence of a .pid file named for the script (generally stored in /var/run/). If it doesn't exist, create it setting its contents to the PID of the process running the script (using getmypid) then continue with normal execution. If it does exist read the PID from it and see if that process is still running, probably by running ps $pid. If it is running, exit. Otherwise, overwrite its contents with your PID (as above) and continue normal execution.

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

When execution finished, delete the file.

执行完成后,删除该文件。

#5


3  

I know this is an old question but in case someone else is looking here I'll post some code. This is what I have done recently in a similar situation and it works well. Put put this code at the top of your file and if the same script is already running it will leave it be and end the new one.

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

I use it to keep a monitoring system running at all times. A cron job starts the script every 5 minutes but unless the other has stopped from some reason (usually if it has crashed, which is very rare!) the new one will just exit itself.

我用它来保持监控系统始终运行。一个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());
}

It will NOT work without modification on Windows, but should be fine in unix based systems.

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

#6


3  

You can use new Symfony 2.6 LockHandler

您可以使用新的Symfony 2.6 LockHandler

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

#7


0  

This worked for me. Set a database record with a lock flag and a time stamp. My script should complete well within 15min so added that as a last locked feild to check:

这对我有用。使用锁定标志和时间戳设置数据库记录。我的脚本应该在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;
            ");

}

Then unlock it at the end of the script:

然后在脚本的末尾解锁它:

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

#8


0  

I have tried this. Works well in Windows too.

我试过这个。在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');

Thanks to @martinodf for his idea.

感谢@martinodf的想法。

#9


-1  

I know this is an old question, but there's an approach which hasn't been mentioned before that I think is worth considering.

我知道这是一个老问题,但有一种方法在我认为值得考虑之前没有提到过。

One of the problems with a lockfile or database flag solution, as already mentioned, is that if the script fails for some reason other than normal completion it won't release the lock. And therefore the next instance won't start until the lock is either manually cleared or cleared by a clean-up function.

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

If, though, you are certain that the script should only ever be running once, then it's relatively easy to check from within the script whether it is already running when you start it. Here's some code:

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

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

Simply call this function at the start of the script, before you do anything else (such as open a database handler or process an import file), and if the same script is already running then it will appear twice in the process list - once for the previous instance, and once for this one. So, if it appears more than once, just exit.

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

A potential gotcha here: I'm assuming that you will never have two scripts with the same basename that may legitimately run simultaneously (eg, the same script running under two different users). If that is a possibility, then you'd need to extend the checking to something more sophisticated than a simple substring on the file's basename. But this works well enough if you have unique filenames for your scripts.

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