如何检测一个 PHP 脚本是否已经在运行?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4140552/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to detect whether a PHP script is already running?
提问by user77413
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 脚本运行?
采纳答案by MartinodF
If you need it to be absolutely crash-proof, you should use semaphores, which are released automatically when php ends the specific request handling.
如果你需要它绝对防崩溃,你应该使用semaphores,当 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.
没有“银弹”,这取决于您的需求。
回答by Mark Amery
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.
只需将锁定文件的路径设置为您喜欢的任何位置即可。
回答by user3400219
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;
}
回答by Brenton Alker
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.
执行完成后,删除文件。
回答by Luke Cousins
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 的系统中应该没问题。
回答by Nicklasos
You can use new Symfony 2.6 LockHandler. Source
你可以使用新的Symfony 2.6 LockHandler。 来源
$lock = new LockHandler('update:contents');
if (!$lock->lock()) {
echo 'The command is already running in another process.';
}
回答by Mark Goodge
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.
这里有一个潜在的问题:我假设您永远不会有两个具有相同基名的脚本可以合法地同时运行(例如,在两个不同的用户下运行相同的脚本)。如果这是可能的,那么您需要将检查扩展到比文件基本名称上的简单子字符串更复杂的内容。但是,如果您的脚本具有唯一的文件名,这将非常有效。
回答by Joshy Francis
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 的想法。
回答by James F
Assuming this is a linux server and you have cronjobs available
假设这是一个 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
script file
脚本文件
#/usr/bin/php /path/to/your/php/script.php
回答by Ralph Vugts
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;");