【问题标题】:Is it possible to terminate a code part when it's execution time is getting longer than a specified time limit?当代码部分的执行时间超过指定的时间限制时,是否可以终止代码部分?
【发布时间】:2014-04-20 21:35:20
【问题描述】:

我目前正在我的 CMS 应用程序中处理计划任务(如 cronjobs)系统。我想知道当一段代码的执行时间超过指定的时间限制时是否有可能终止它。

一些计划任务可能很繁重,一个任务可能会陷入无限循环,以保持简短;任务可能占用大量时间的原因有很多。与此有关的少数问题之一是该任务会占用比实际需要更多的资源,这会降低运行它的服务器的性能,这会使 CMS 和它自身的站点不那么可靠。

将系统想象如下:一旦触发了计划任务管理器,它将执行在该时间安排的所有任务。想象一个简单的 for 循环,它会一个接一个地同步执行每个任务。想想下面这个简单的例子:

// This array would contain an array with all scheduled tasks that should be executed.
$tasks = Array();

// Loop through each task
foreach($tasks as $t) {
    // Execute the task
    $t->execute();
}

现在,$task->execute(); 方法让 PHP 的解释器等待,直到任务的代码被执行。我的问题是,如果执行时间超过指定时间,是否有任何可能的方法来终止在 execute(); 方法后面运行的代码。

例如,如果提供了 10 秒的时间限制,并且任务执行其代码的时间超过 30 秒,则任务的运行代码应在 10 秒后终止,以允许任务调度程序继续执行下一个任务。

可以使用一个简单的 if 语句在每个计划任务的代码中实现此功能,该语句会在每次执行部分代码时检查时间。这样做的问题是,这需要在每个任务的代码中实现,因为该系统旨在供第 3 方开发人员使用这些类型的任务来正确使用他们的插件来实现这些任务。我不喜欢“强迫”所有使用这些任务的开发人员来实现这些功能。

我知道 PHP 提供的 set_time_limit(); 方法,但问题是虽然这个方法会终止整个会话而不是那个方法,这就是为什么这不能解决问题。

【问题讨论】:

  • stackoverflow.com/questions/10587323/timeout-a-function-in-php 非常相似的问题。简而言之:默认情况下 PHP 是单线程的,因此您无法从脚本本身控制执行。但是,有一些非默认扩展,例如 pthread,它允许您生成一个工作线程并从您的主线程中再次杀死它。
  • 非常感谢您的回答,以及与此问题相关的另一个问题的链接。这基本上意味着这是不可能的,主要是因为我不想使用一些扩展,因为系统应该能够在任何 Apache 服务器上运行而不会出现问题。不过,我会研究一下这个方法,因为我可能希望有选择地实现这个功能。再次感谢您的回答。您想将此作为答案发布,以便我接受吗?

标签: php timeout scheduled-tasks limit


【解决方案1】:

“移植”我对答案的评论:

This other question 非常相似。

默认情况下,PHP 在单线程中运行脚本,因此不可能从脚本本身取消代码执行。
只有 非常非常 少数 PHP 函数支持异步操作,例如您可以使用mysqlnd 运行异步查询,但是此特定功能是 mysql 驱动程序的一部分,而不是 PHP。

有一些扩展将多线程引入 PHP,即pthread
要推荐另一个来源,请阅读扩展作者的this answer

不幸的是,对于“主流”项目(应该在尽可能通用的设置上尽可能好地运行),使用pthreads 是不可行的。
该扩展不太可能安装在任何共享的网络空间上。

另外,请记住,线程安全编程(或脚本对于那些关心的人)是一项严肃的任务,你必须关心它。这意味着准备好您的线程代码,以免彼此锁定等待(死锁)、破坏彼此的数据等。
关于thread-safety这个术语的详细解答可以在here找到。

彻底考虑所涉及的工作、搞砸的风险以及使用线程的好处。
确保你的愿望真的很重要,足以引入复杂的线程。

【讨论】:

    【解决方案2】:

    我遇到过这种问题,我有一个技巧可以解决这个问题。我的解决方法是,我使用 cURL 执行本地脚本,如下所示:

    // This is the curl call which will execute the local script
    $execute_script = curl_call('local_script.php');
    

    在'local_script.php'文件中是要执行的代码

    // In local_script.php
    // Set time limit
    set_time_limit(10);
    // Execute task
    $task->execute();
    // Print 1 to let the curl call know that the script executed at the right time
    echo '1';
    

    然后,在 cURL 调用部分,您可以检查脚本在返回“1”时是否正常执行

    if ($execute_script == '1'){
      // Successful script
    } else {
      // Timed out
    }
    

    希望有帮助

    【讨论】:

    • 我真的很喜欢这个解决方案,但我认为这行不通。据我所知,这将创建一个新会话。执行任务的会话已初始化所有必需的类,例如数据库、注册表、缓存和其他类型的类,这些类由任务中运行的代码使用。创建一个新的会话来运行任务将不允许他们访问他们应该使用的正确的类实例。
    【解决方案3】:

    如果超过时间,你可以打破循环:

    $max = 10; //maximum time in seconds
    $start = microtime(true);
    foreach($tasks as $t) {
        // Execute the task
        $t->execute();
        if (microtime(true) - $start > $max) {
            break;
        }
    }
    

    【讨论】:

    • 这只有在执行时间很短的小任务很多的情况下才能解决问题。我遇到的问题是某些任务可能会卡住、陷入无限循环或类似情况。因为 PHP 的解释器卡在 $task->execute(); 方法上,所以检查是否花费太长时间的 if 语句甚至不会执行,因为它仍在执行该方法中的代码。因此,如果任务执行时间是 30 秒,而提供的时间限制是 10 秒,那么这种方法无法解决问题。
    • 在这种情况下,您可以修改它以在对象内部或通常以无限循环结束的任何内容中工作。我不知道有任何方法可以立即停止,无论它在执行中的哪个位置。
    • 这个我知道了,这个计划任务系统的主要目的是让第三方开发者可以正确地安排他们的插件所需的任务,如果这些开发者不必实现这个就更好了他们自己的特色。无论如何,谢谢你的回答。需要注意的一件事是,您的代码似乎出错了,foreach 循环提供了一个$t 变量,而循环内的代码使用$task,如果您能纠正这个问题,那就太好了,以防止未来的问题。再次感谢您的回答。
    • 哦,对不起。我只是直接从你那里复制过来的。
    • 哎呀,显然是我自己做的,对不起,现在解决问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-17
    • 1970-01-01
    • 2014-04-01
    • 1970-01-01
    相关资源
    最近更新 更多