10

目前,我试图阻止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 的一个实例正在运行?

ps 我的/tmp/文件夹没有清理。ls -al /tmp/*.lock显示锁定文件是在第一天创建的:

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

7 回答 7

12

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手册中的注释

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

这是手册页O_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再次运行而不会出现问题。

于 2012-12-12T09:53:02.153 回答
9

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

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

于 2012-12-18T06:24:27.437 回答
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 传真”获取所有正在运行的应用程序,检查此文件是否在列表中。

于 2012-12-12T09:12:41.183 回答
0

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

$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";
}
于 2012-12-12T09:16:18.527 回答
0

正如一些人建议的那样,您可以使用锁定文件,但您真正需要的是 PHP Semaphore函数。这些有点像文件锁,但专为您正在做的事情而设计,限制对共享资源的访问。

于 2012-12-28T01:06:15.947 回答
0

切勿将 unlink 用于锁定文件或重命名等其他功能。它破坏了 Linux 上的 LOCK_EX。例如,在取消链接或重命名锁定文件后,任何其他脚本总是从flock() 中获取true。

检测先前有效结束的最佳方法 - 在 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;
}
于 2014-01-04T20:22:21.047 回答
0

在 galimzhan 的答案中添加了对旧锁的检查(没有足够的 *s 来评论),这样如果进程死亡,旧的锁文件将在三分钟后被清除,让 cron 再次启动进程。这就是我使用的:

<?php
$lock = '/tmp/myscript.lock';
if(time()-filemtime($lock) > 180){
    // remove stale locks older than 180 seconds
    unlink($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);
}

您还可以为 cron 作业添加超时,以便 php 进程在 60 秒后被终止,例如:

* * * * * user timeout -s 9 60 php /dir/process.php >/dev/null
于 2022-02-05T14:39:56.437 回答