如何防止PHP脚本多次运行?

时间:2012-12-12 09:10:20

标签: php linux cron centos

目前,我试图阻止onlytask.php脚本多次运行:

$fp = fopen("/tmp/"."onlyme.lock", "a+");
if (flock($fp, LOCK_EX | LOCK_NB)) {
  echo "task started\n";
  //
    while (true) {
      // do something lengthy
      sleep(10);
    }
  //
  flock($fp, LOCK_UN);
} else {
  echo "task already running\n";
}
fclose($fp);

并且每分钟执行上述脚本都有一个cron作业:

* * * * * php /usr/local/src/onlytask.php

它有效一段时间了。几天后,当我这样做时:

ps auxwww | grep onlytask

我发现有两个实例正在运行!不是三个或更多,而不是一个。我杀死了其中一个实例。几天后,再次有两个实例。

代码有什么问题?还有其他选择只限制onlytask.php的一个实例正在运行吗?

P.S。我的/tmp/文件夹未清除。 ls -al /tmp/*.lock显示锁定文件是在第一天创建的:

-rw-r--r--  1 root root    0 Dec  4 04:03 onlyme.lock

6 个答案:

答案 0 :(得分:10)

打开锁定文件时应使用x标志:

<?php

$lock = '/tmp/myscript.lock';
$f = fopen($lock, 'x');
if ($f === false) {
  die("\nCan't acquire lock\n");
} else {
  // Do processing
  while (true) {
    echo "Working\n";
    sleep(2);
  }
  fclose($f);
  unlink($lock);
}

来自PHP manual

的注释
  

' x ' - 仅限写作创建和打开;将文件指针放在   文件的开头。如果文件已存在,则调用fopen()   将返回FALSE并生成级别错误   E_WARNING。如果该文件不存在,请尝试创建它。这是   相当于为底层指定O_EXCL | O_CREAT标志   open(2)系统调用。

以下是来自man pageO_EXCL解释:

  

O_EXCL - 如果设置了O_CREAT和O_EXCL,则open()如果文件失败   存在。检查文件是否存在以及创建   文件如果不存在相对于其他文件应该是原子的   执行open()的线程在同一个文件名中命名相同的文件名   设置了O_EXCL和O_CREAT的目录。如果设置了O_EXCL和O_CREAT,   和路径名称是一个符号链接,open()将失败并将errno设置为   [EEXIST],无论符号链接的内容如何。如果是O_EXCL   设置并且未设置O_CREAT,结果未定义。

<强>更新

更可靠的方法 - 运行主脚本,它获取锁,运行工作脚本并释放锁。

<?php
// File: main.php

$lock = '/tmp/myscript.lock';
$f = fopen($lock, 'x');
if ($f === false) {
  die("\nCan't acquire lock\n");
} else {
  // Spawn worker which does processing (redirect stderr to stdout)
  $worker = './worker 2>&1';
  $output = array();
  $retval = 0;
  exec($worker, $output, $retval);
  echo "Worker exited with code: $retval\n";
  echo "Output:\n";
  echo implode("\n", $output) . "\n";
  // Cleanup the lock
  fclose($f);
  unlink($lock);
}

这是工人。让我们在其中引发一个假的致命错误:

#!/usr/bin/env php
<?php
// File: worker (must be executable +x)
for ($i = 0; $i < 3; $i++) {
  echo "Processing $i\n";
  if ($i == 2) {
    // Fake fatal error
    trigger_error("Oh, fatal error!", E_USER_ERROR);
  }
  sleep(1);
}

这是我得到的输出:

galymzhan@atom:~$ php main.php 
Worker exited with code: 255
Output:
Processing 0
Processing 1
Processing 2
PHP Fatal error:  Oh, fatal error! in /home/galymzhan/worker on line 8
PHP Stack trace:
PHP   1. {main}() /home/galymzhan/worker:0
PHP   2. trigger_error() /home/galymzhan/worker:8

重点是锁定文件正确清理,这样您就可以毫无问题地再次运行main.php

答案 1 :(得分:6)

现在我检查进程是否由ps运行并通过bash脚本扭曲php脚本:

 #!/bin/bash

 PIDS=`ps aux | grep onlytask.php | grep -v grep`
 if [ -z "$PIDS" ]; then
     echo "Starting onlytask.php ..."
     php /usr/local/src/onlytask.php >> /var/log/onlytask.log &
 else
     echo "onlytask.php already running."
 fi

并每分钟bash运行cron脚本。

答案 2 :(得分:1)

<?php

$sLock = '/tmp/yourScript.lock';

if( file_exist($sLock) ) {
 die( 'There is a lock file' );
}

file_put_content( $sLock, 1 );

// A lot of code

unlink( $sLock );

您可以通过编写pid添加额外的检查,然后在file_exist-statement中进行检查。 为了更加安全,您可以通过“ps fax”结束检查所有正在运行的应用程序,检查此文件是否在列表中。

答案 3 :(得分:0)

尝试使用文件的存在而不是它的flock标志:

$lockFile = "/tmp/"."onlyme.lock";
if (!file_exists($lockFile)) {

  touch($lockFile); 

  echo "task started\n";
  //
  // do something lengthy
  //

  unlink($lockFile); 

} else {
  echo "task already running\n";
}

答案 4 :(得分:0)

您可以像某些人建议的那样使用锁定文件,但您真正需要的是PHP Semaphore函数。这些类似于文件锁,但专门针对您正在进行的操作而设计,限制了对共享资源的访问。

答案 5 :(得分:0)

永远不要对锁文件或重命名等其他功能使用取消链接。它打破了Linux上的LOCK_EX。例如,在取消链接或重命名锁定文件后,任何其他脚本始终从flock()获得。

检测上一个有效结束的最佳方法 - 在LOCK_UN处理之前,在结束锁上写入锁定文件的几个字节。并且在LOCK_EX从锁定文件和ftruncate句柄中读取几个字节后。

重要提示:所有测试均在Linux上的PHP 5.4.17和Windows 7上的5.4.22上进行测试。

示例代码:

设置信号量:

$handle = fopen($lockFile, 'c+');
if (!is_resource($handle) || !flock($handle, LOCK_EX | LOCK_NB)) {
    if (is_resource($handle)) {
        fclose($handle);
    }
    $handle = false;
    echo SEMAPHORE_DENY;
    exit;
} else {
    $data = fread($handle, 2);
    if ($data !== 'OK') {
        $timePreviousEnter = fileatime($lockFile);
        echo SEMAPHORE_ALLOW_AFTER_FAIL;
    } else {
        echo SEMAPHORE_ALLOW;
    }
    fseek($handle, 0);
    ftruncate($handle, 0);
}

留下信号量(在关机处理程序中更好的调用):

if (is_resource($handle)) {
    fwrite($handle, 'OK');
    flock($handle, LOCK_UN);
    fclose($handle);
    $handle = false;
}