【问题标题】:PHP locking / making sure a given script is only running once at any given timePHP锁定/确保给定脚本在任何给定时间只运行一次
【发布时间】:2012-01-31 16:55:54
【问题描述】:

我正在尝试编写一个 PHP 脚本,我想确保它在任何给定时间都只有一个实例在运行。所有这些关于锁定的不同方式、竞争条件等的讨论都让我感到沮丧。

我很困惑锁定文件是要走的路,还是信号量,还是使用 MySQL 锁,等等等等。

谁能告诉我:

a) 实现这个的正确方法是什么?

b) 指向一个 PHP 实现(或易于移植到 PHP 的东西?)

【问题讨论】:

  • 脚本是在网络服务器还是命令行下运行?
  • 如果它真的很重要......网络服务器。
  • 可能是一个 mysql 锁,在这种情况下,脚本可以优雅地中止并说“已在使用中”。使用flock之类的东西会将脚本与apache本身锁定,并可能导致500个内部错误等等。

标签: php locking


【解决方案1】:

一种方法是将 php 函数 flock 与一个虚拟文件一起使用,该文件将充当看门狗。

在我们的工作开始时,如果文件引发 LOCK_EX 标志,则可以退出或等待。

PHP 群文档:http://php.net/manual/en/function.flock.php

对于本示例,必须首先创建一个名为 lock.txt 的文件。

示例1,如果另一个孪生进程正在运行,它将正常退出,无需重试,并给出状态消息。

如果文件 lock.txt 不可访问,它会抛出错误状态。

<?php

$fp = fopen("lock.txt", "r+");

if (!flock($fp, LOCK_EX|LOCK_NB, $blocked)) {
    if ($blocked) {

        // another process holds the lock
        echo "Couldn't get the lock! Other script in run!\n"; 

    }
    else {
        // couldn't lock for another reason, e.g. no such file
        echo "Error! Nothing done.";
    }
}
else {
    // lock obtained
    ftruncate($fp, 0);  // truncate file

    // Your job here 
    echo "Job running!\n";
    
    // Leave a breathe
    sleep(3);

    fflush($fp);            // flush output before releasing the lock
    flock($fp, LOCK_UN);    // release the lock

}

fclose($fp); // Empty memory

示例 2FIFO(先进先出):我们希望进程等待队列之后的执行,如果有的话:

<?php

$fp = fopen("lock.txt", "r+");

if (flock($fp, LOCK_EX)) {  // acquire an exclusive lock
    ftruncate($fp, 0);      // truncate file

    // Your job here 
    echo "Job running!\n";

    // Leave a breathe
    sleep(3);

    fflush($fp);            // flush output before releasing the lock
    flock($fp, LOCK_UN);    // release the lock
}

fclose($fp);

也可以将 fopen 置于 x 模式,通过在脚本结束时创建和擦除文件。

创建和打开仅供写作;将文件指针放在 文件的开头。如果文件已经存在,则 fopen() 调用 将返回 FALSE 失败

http://php.net/manual/en/function.fopen.php


但是,进入 Unix 环境,为了进行微调,我发现将每个带有 getmypid() 的后台脚本的 PID 列出到数据库或单独的 JSON 文件中更容易。

当一个任务结束时,脚本负责在这个文件中声明他的状态(eq:success/failure/debug infos等),然后删除他的PID。在我看来,这允许以更简单的方式创建管理工具和守护进程。并在必要时使用posix_kill() 从 PHP 中杀死一个 PID。

微服务是使用类 Unix 管道组成的。 服务可以调用服务。 https://en.wikipedia.org/wiki/Microservices


另请参阅: Prevent PHP script using up all resources while it runs?

【讨论】:

    【解决方案2】:
    // borrow from 2 anwsers on stackoverflow
    function IsProcessRunning($pid) {
        return shell_exec("ps aux | grep " . $pid . " | wc -l") > 2;
    }
    
    function AmIRunning($process_file) {
        // Check I am running from the command line
        if (PHP_SAPI != 'cli') {
            error('Run me from the command line');
            exit;
        }
    
        // Check if I'm already running and kill myself off if I am
        $pid_running = false;
        $pid = 0;
        if (file_exists($process_file)) {
            $data = file($process_file);
            foreach ($data as $pid) {
                $pid = (int)$pid;
                if ($pid > 0 && IsProcessRunning($pid)) {
                    $pid_running = $pid;
                    break;
                }
            }
        }
        if ($pid_running && $pid_running != getmypid()) {
            if (file_exists($process_file)) {
                file_put_contents($process_file, $pid);
            }
            info('I am already running as pid ' . $pid . ' so stopping now');
            return true;
        } else {
            // Make sure file has just me in it
            file_put_contents($process_file, getmypid());
            info('Written pid with id '.getmypid());
            return false;
        }
    }
    
    /*
     * Make sure there is only one instance running at a time
     */
    $lockdir = '/data/lock';
    $script_name = basename(__FILE__, '.php');
    // The file to store our process file
    $process_file = $lockdir . DS . $script_name . '.pid';
    
    $am_i_running = AmIRunning($process_file);
    if ($am_i_running) {
        exit;
    }
    

    【讨论】:

      【解决方案3】:

      使用信号量:

      $key = 156478953; //this should be unique for each script
      $maxAcquire = 1;
      $permissions =0666;
      $autoRelease = 1; //releases semaphore when request is shut down (you dont have to worry about die(), exit() or return
      $non_blocking = false; //if true, fails instantly if semaphore is not free
      
      $semaphore = sem_get($key, $maxAcquire, $permissions, $autoRelease);
      if (sem_acquire($semaphore, $non_blocking ))  //blocking (prevent simultaneous multiple executions)
      {
          processLongCalculation();
      }
      sem_release($semaphore);
      

      见:

      https://www.php.net/manual/en/function.sem-get.php

      https://www.php.net/manual/en/function.sem-acquire.php

      https://www.php.net/manual/en/function.sem-release.php

      【讨论】:

        【解决方案4】:

        您可以选择最适合您的项目的解决方案,实现该解决方案的两种简单方法是文件锁定或数据库锁定。

        有关文件锁定的实现,请查看http://us2.php.net/flock

        如果您已经使用数据库,请创建一个表,为该脚本生成已知令牌,将其放在那里,然后在脚本结束后将其删除。为避免出现错误问题,您可以使用到期时间。

        【讨论】:

        • 您确定这是一种安全的处理方式吗? PHP.net 上的 cmets 有几个条目表明它可能会引入竞争条件。
        【解决方案5】:

        【讨论】:

          【解决方案6】:

          如果你在linux上使用php,我认为最实用的方法是:

          <?php
           if(shell_exec('ps aux | grep '.__FILE__.' | wc  -l')>3){
              exit('already running...');
           }
          ?>
          

          另一种方法是使用文件标志和退出回调, 退出回调将确保文件标志将在任何情况下重置为 0 php 执行结束也是致命错误。

          <?php
          function exitProcess(){
            if(file_get_contents('inprocess.txt')!='0'){
              file_put_contents('inprocess.txt','0');  
            }
          }
          
          if(file_get_contents('inprocess.txt')=='1'){
            exit();
          }
          
          file_put_contents('inprocess.txt','1');
          register_shutdown_function('exitProcess');
          
          /**
          execute stuff
          **/
          ?>
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-07-08
            • 2020-10-24
            • 2010-10-31
            • 1970-01-01
            • 1970-01-01
            • 2015-05-19
            • 1970-01-01
            • 2016-11-29
            相关资源
            最近更新 更多