【问题标题】:How to prevent the cron job execution, if it is already running如何防止 cron 作业执行(如果它已经在运行)
【发布时间】:2012-05-20 02:08:09
【问题描述】:

我有一个 php 脚本,我在 CentOS 上每 10 分钟通过 cron 执行这个脚本。

问题在于,如果 cron 作业需要超过 10 分钟,那么同一 cron 作业的另一个实例将启动。

我尝试了一个技巧,那就是:

  1. 使用 php 代码创建了一个锁定文件(与 pid 文件相同) cron 作业开始了。
  2. 在作业完成时删除了带有 php 代码的锁定文件。
  3. 当任何新的 cron 作业开始执行脚本时,我检查了是否锁定 文件存在,如果存在,则中止脚本。

但是可能存在一个问题,即当锁定文件由于某种原因没有被脚本删除或删除时。 cron 将永远不会重新启动。

如果 cron 作业已经在运行,我有什么办法可以再次停止它的执行,使用 Linux 命令或类似的命令?

【问题讨论】:

  • (编辑)我的建议只是作为一种解决方案发布,以防flock() 不起作用,我建议您先尝试Jack 的解决方案。 我已经测试过使用flock() 但由于某种原因并没有停止 cron 脚本的多个实例。所以经过一番搜索,我找到了这个类并稍微修改了它以供我自己使用:abhinavsingh.com/blog/2009/12/…
  • 使它成为一个服务:forever() { do_stuff(); sleep_10_minutes(); }(伪代码)
  • @Leonid Shagabutdinov 的回答应该被认为是解决这个问题的正确方法。

标签: php cron centos


【解决方案1】:

咨询锁定正是为此目的而设计的。

您可以使用flock() 完成建议锁定。只需将该函数应用于先前打开的锁定文件,以确定另一个脚本是否对其进行了锁定。

$f = fopen('lock', 'w') or die ('Cannot create lock file');
if (flock($f, LOCK_EX | LOCK_NB)) {
    // yay
}

在这种情况下,我添加了LOCK_NB 以防止下一个脚本等到第一个脚本完成。由于您使用的是 cron,因此总会有下一个脚本。

如果当前脚本提前终止,任何文件锁都会被操作系统释放。

【讨论】:

  • 感谢杰克的好回答。这是最好的解决方案。但我只是担心如果 cron 因任何原因停止(可能是电源故障或系统重启)并且无法释放锁定。比 cron 不会再次启动,因为它无法再次获取锁。我想我应该先试试这个:)
  • 不可能。无论进程何时停止,操作系统都会释放锁定,为此 h00ligan 的链接文章需要一些丑陋的本地化破解。
  • 这应该是公认的答案。它完全按照我的预期工作。谢谢分配杰克。
【解决方案2】:

如果可以配置,或许最好不要写代码:

https://serverfault.com/questions/82857/prevent-duplicate-cron-jobs-running

【讨论】:

    【解决方案3】:

    flock() 对我来说效果很好 - 我有一个每 5 分钟安排一次数据库请求的 cron 作业,因此不能同时运行多个请求至关重要。这就是我所做的:

    $filehandle = fopen("lock.txt", "c+");
    
    if (flock($filehandle, LOCK_EX | LOCK_NB)) {
        // code here to start the cron job
       flock($filehandle, LOCK_UN);  // don't forget to release the lock
    } else {
        // throw an exception here to stop the next cron job
    }
    
    fclose($filehandle);
    

    如果您不想终止下一个计划的 cron 作业,而只是暂停它直到正在运行的作业完成,那么只需省略 LOCK_NB

    if (flock($filehandle, LOCK_EX)) 
    

    【讨论】:

      【解决方案4】:

      这是一个非常常见的问题,有一个非常简单的解决方案:cronjoblock 一个简单的 8 行 shellscript 包装器使用flock 应用锁定:

      https://gist.github.com/coderofsalvation/1102e56d3d4dcbb1e36f

      顺便说一句。 cronjoblock 还可以逆转 cron 的垃圾邮件行为:仅在出现问题时才输出。这对于 cron 的 MAILTO 变量很方便。 stdout/stderr 输出将被抑制(因此 cron 不会发送邮件)除非给定进程的退出代码 > 0

      【讨论】:

        【解决方案5】:

        flock 在 php 5.3.3 中将不起作用,因为文件资源句柄关闭时的自动解锁已被删除。现在解锁总是必须手动完成。

        【讨论】:

          【解决方案6】:

          我用这个 ::

          <?php
          // Create a PID file
          if (is_file (dirname ($_SERVER['SCRIPT_NAME']) . "/.processing")) { die (); }
          file_put_contents (dirname ($_SERVER['SCRIPT_NAME']) . "/.processing", "processing");
          
          // SCRIPT CONTENTS GOES HERE //
          
          @unlink (dirname ($_SERVER['SCRIPT_NAME']) . "/.processing");
          ?>
          

          【讨论】:

          • 如果您的脚本因任何原因崩溃,cron 将永远不会再次运行。
          【解决方案7】:
          #!/bin/bash
          ps -ef | grep -v grep | grep capture_12hz_sampling_track.php
          if [ $? -eq 1 ];
          then
               nohup /usr/local/bin/php /opt/Apache/htdocs/cmsmusic_v2/script/Mp3DownloadProcessMp4/capture_12hz_sampling_track.php &
          else
                echo "Already running"
          fi
          

          【讨论】:

            【解决方案8】:

            另一种选择:

            <?php
            
            /**
            * Lock manager to ensure our cron doesn't run twice at the same time.
            *
            * Inspired by the lock mechanism in Mage_Index_Model_Process
            *
            * Usage:
            * 
            * $lock = Mage::getModel('stcore/cron_lock');
            *
            * if (!$lock->isLocked()) {
            *      $lock->lock();
            *      // Do your stuff
            *      $lock->unlock();
            * }
            */
            class ST_Core_Model_Cron_Lock extends Varien_Object
            {
                /**
                 * Process lock properties
                 */
                protected $_isLocked = null;
                protected $_lockFile = null;
            
                /**
                 * Get lock file resource
                 *
                 * @return resource
                 */
                protected function _getLockFile()
                {
                    if ($this->_lockFile === null) {
                        $varDir = Mage::getConfig()->getVarDir('locks');
                        $file = $varDir . DS . 'stcore_cron.lock';
                        if (is_file($file)) {
                            $this->_lockFile = fopen($file, 'w');
                        } else {
                            $this->_lockFile = fopen($file, 'x');
                        }
                        fwrite($this->_lockFile, date('r'));
                    }
                    return $this->_lockFile;
                }
            
                /**
                 * Lock process without blocking.
                 * This method allow protect multiple process runing and fast lock validation.
                 *
                 * @return Mage_Index_Model_Process
                 */
                public function lock()
                {
                    $this->_isLocked = true;
                    flock($this->_getLockFile(), LOCK_EX | LOCK_NB);
                    return $this;
                }
            
                /**
                 * Lock and block process.
                 * If new instance of the process will try validate locking state
                 * script will wait until process will be unlocked
                 *
                 * @return Mage_Index_Model_Process
                 */
                public function lockAndBlock()
                {
                    $this->_isLocked = true;
                    flock($this->_getLockFile(), LOCK_EX);
                    return $this;
                }
            
                /**
                 * Unlock process
                 *
                 * @return Mage_Index_Model_Process
                 */
                public function unlock()
                {
                    $this->_isLocked = false;
                    flock($this->_getLockFile(), LOCK_UN);
                    return $this;
                }
            
                /**
                 * Check if process is locked
                 *
                 * @return bool
                 */
                public function isLocked()
                {
                    if ($this->_isLocked !== null) {
                        return $this->_isLocked;
                    } else {
                        $fp = $this->_getLockFile();
                        if (flock($fp, LOCK_EX | LOCK_NB)) {
                            flock($fp, LOCK_UN);
                            return false;
                        }
                        return true;
                    }
                }
            
                /**
                 * Close file resource if it was opened
                 */
                public function __destruct()
                {
                    if ($this->_lockFile) {
                        fclose($this->_lockFile);
                    }
                }
            }
            

            来源:https://gist.github.com/wcurtis/9539178

            【讨论】:

              【解决方案9】:

              我正在运行一个 php cron 作业脚本,该脚本专门处理使用现有 API 发送文本消息。在我的本地机器上,cron 工作运行良好,但在我客户的机器上,它正在发送双重消息。虽然这对我来说没有意义,但我仔细检查了负责发送消息的文件夹的权限,并将权限设置为 root。一旦我将所有者设置为 www-data (Ubuntu),它就会开始正常运行。

              这可能不是你的问题,但如果它是一个简单的 cron 脚本,我会仔细检查权限。

              【讨论】: