cron 调度实际上是如何实现并确保脚本按时运行的?

cron 调度实际上是如何实现并确保脚本按时运行的?

我想问一下有关 cron 作业的问题。好的,我们将脚本放入 crontab 中,然后 cron 守护进程执行它们。

现在,如果我理解这一点,每分钟,cron 都会检查每个用户的 crontab 并执行配置的脚本。但这实际上是如何完成的呢?它是否分叉子进程等?

它不能按顺序执行任务,因为时间会丢失(例如,由于等待长时间运行的脚本完成)。那么这实际上是如何实现的呢?

只是为了提供帮助,我不是在寻找低级代码。对我来说,高级描述(也许是算法?)或如何在大多数发行版中实现就足够了。

答案1

我在 StackOverflow 上发现了这个问答,标题为:cron 内部如何调度作业?

摘录自该帖子和维基百科关于 cron 的文章

The algorithm used by this cron is as follows:

1. On start-up, look for a file named .crontab in the home directories of 
   all account holders.

2. For each crontab file found, determine the next time in the future that
   each command is to be run.

3. Place those commands on the Franta-Maly event list with their corresponding
   time and their "five field" time specifier.

4. Enter main loop:

   1. Examine the task entry at the head of the queue, compute how far in 
      the future it is to be run.

   2. Sleep for that period of time.

   3. On awakening and after verifying the correct time, execute the task 
      at the head of the queue (in background) with the privileges of the 
      user who created it.

   4. Determine the next time in the future to run this command and place 
      it back on the event list at that time

此超级用户问答标题为:克朗是如何工作的?涵盖了您的一些其他问题。例如,您的问题是 cron 如何处理安排在同一时间的作业。该线程中的答案之一指出,当 cron 守护进程处理每个任务时,它会分叉每个计划的作业,以便没有任何单个作业会充当具有重叠时间的作业的阻止程序。

答案2

我写了一个博客文章描述它。
引用那里的相关文字:

  • PriorityBlockingQueue我们可以拥有一个有限的线程池,它将通过从优先级为 的(线程安全堆)中选取所有任务来执行所有任务job.nextExecutionTime()
  • 这意味着该堆的顶部元素将始终是最快触发的元素。
  • 我们将遵循标准线程池生产者-消费者模式。
  • 我们将有一个线程,该线程将在无限循环中运行,并在使用队列中的新作业后将其提交到线程池。我们称之为队列消费者线程:
void goToSleep(job, jobQueue){
    jobQueue.push(job);
    sleep(job.nextExecutionTime() - getCurrentTime());
}

void executeJob(job, jobQueue){
    threadpool.submit(job); // async call
    job = job.copy();
    job.setNextExecutionTime(getCurrentTime() + job.getExecutionInterval());
    jobQueue.add(job);
}

@Override
void run(){
    while(true)
    {
        job = jobQueue.pop()
        if(job.nextExecutionTime() > getCurrentTime()){
            // Nothing to do
            goToSleep(job, jobQueue)
        }
        else{
            executeJob(job, jobQueue)
        }
    }
}
  • 还有一个线程将监视 crontab 文件中是否有任何新添加的作业,并将它们推送到队列中。
  • 我们称之为队列生产者线程:
@Override
void run()
{
    while(true)
    {
        newJob = getNewJobFromCrontabFile() // blocking call
        jobQueue.push(newJob)
    }
}
  • 然而,这样做有一个问题:
    • 想象一下 Thread1 正在睡觉,一个小时后就会醒来。
    • 与此同时,一个新任务到来,该任务应该每分钟运行一次。
    • 这个新任务要在一小时后才能开始执行。
  • 为了解决这个问题,只要新任务必须比队列中的前端任务运行得早,我们就可以让 ProducerThread 将 ConsumerThread 从睡眠中强制唤醒:
@Override
void run()
{
    while(true)
    {
        newJob = getNewJobFromCrontabFile() // blocking call
        jobQueue.push(newJob)
        if(newJob == jobQueue.peek())
        {
            // The new job is the one that will be scheduled next.
            // So wakeup consumer thread so that it does not oversleep.
            jobQueueConsumerThread.interrupt()
        }
    }
}

相关内容