在设置 set_time_limit() 时,sleep() 的持续时间将被忽略在执行时间内。以下说明
<?php
set_time_limit(20);
while ($i<=10)
{
echo "i=$i ";
sleep(100);
$i++;
}
?>
输出
i=0 i=1 i=2 i=3 i=4 i=5 i=6 i=7 i=8 i=9 i=10
(PHP 4, PHP 5, PHP 7, PHP 8)
set_time_limit — 限制最大执行时间
设置脚本允许运行的秒数。如果达到此限制,脚本将返回致命错误。默认限制为 30 秒,或者,如果存在,则为 php.ini 中定义的 max_execution_time
值。
调用时,set_time_limit() 会将超时计数器从零重新开始。换句话说,如果超时时间为默认的 30 秒,并且在脚本执行 25 秒后,进行了诸如 set_time_limit(20)
的调用,则脚本将在超时前总共运行 45 秒。
seconds
最大执行时间,以秒为单位。如果设置为零,则不施加时间限制。
注意:
set_time_limit() 函数和配置指令 max_execution_time 仅影响脚本本身的执行时间。在脚本执行之外发生的活动(例如使用 system() 的系统调用、流操作、数据库查询等)所花费的时间不包括在确定脚本运行的最大时间内。在 Windows 上情况并非如此,因为测量的时是实际时间。
在设置 set_time_limit() 时,sleep() 的持续时间将被忽略在执行时间内。以下说明
<?php
set_time_limit(20);
while ($i<=10)
{
echo "i=$i ";
sleep(100);
$i++;
}
?>
输出
i=0 i=1 i=2 i=3 i=4 i=5 i=6 i=7 i=8 i=9 i=10
set_time_limit(...) 和 ini_set('max_execution_time',...); 都会忽略 sleep、file_get_contents、shell_exec、mysql_query 等的时间消耗,所以我构建了这个函数 my_background_exec(),以便在后台/分离进程中运行静态方法/函数,并在时间超时时将其杀死
my_exec.php
<?php
函数 my_background_exec($function_name, $params, $str_requires, $timeout=600)
{$map=array('"'=>'\"', '$'=>'\$', '`'=>'\`', '\\'=>'\\\\', '!'=>'\!');
$str_requires=strtr($str_requires, $map);
$path_run=dirname($_SERVER['SCRIPT_FILENAME']);
$my_target_exec="/usr/bin/php -r \"chdir('{$path_run}');{$str_requires} \\\$params=json_decode(file_get_contents('php://stdin'),true);call_user_func_array('{$function_name}', \\\$params);\"";
$my_target_exec=strtr(strtr($my_target_exec, $map), $map);
$my_background_exec="(/usr/bin/php -r \"chdir('{$path_run}');{$str_requires} my_timeout_exec(\\\"{$my_target_exec}\\\", file_get_contents('php://stdin'), {$timeout});\" <&3 &) 3<&0";//php 默认使用 "sh",而 "sh" 不支持 "<&0"
my_timeout_exec($my_background_exec, json_encode($params), 2);
}
函数 my_timeout_exec($cmd, $stdin='', $timeout)
{$start=time();
$stdout='';
$stderr='';
//file_put_contents('debug.txt', time().':cmd:'.$cmd."\n", FILE_APPEND);
//file_put_contents('debug.txt', time().':stdin:'.$stdin."\n", FILE_APPEND);
$process=proc_open($cmd, [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes);
if (!is_resource($process))
{return array('return'=>'1', 'stdout'=>$stdout, 'stderr'=>$stderr);
}
$status=proc_get_status($process);
posix_setpgid($status['pid'], $status['pid']); //将进程组 ID (pgid) 与父进程的 pgid 分开
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
fwrite($pipes[0], $stdin);
fclose($pipes[0]);
while (1)
{$stdout.=stream_get_contents($pipes[1]);
$stderr.=stream_get_contents($pipes[2]);
if (time()-$start>$timeout)
{//proc_terminate($process, 9); //仅终止子进程,不会终止子子进程
posix_kill(-$status['pid'], 9); //向组内的所有进程发送 SIGKILL 信号(负数表示 GPID,所有子进程共享顶级进程组,除了嵌套的 my_timeout_exec)
//file_put_contents('debug.txt', time().":kill group {$status['pid']}\n", FILE_APPEND);
return array('return'=>'1', 'stdout'=>$stdout, 'stderr'=>$stderr);
}
$status=proc_get_status($process);
//file_put_contents('debug.txt', time().':status:'.var_export($status, true)."\n";
if (!$status['running'])
{fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
return $status['exitcode'];
}
usleep(100000);
}
}
?>
a_class.php
<?php
类 A
{
静态函数 jack($a, $b)
{sleep(4);
file_put_contents('debug.txt', time().":A::jack:".$a.' '.$b."\n", FILE_APPEND);
sleep(15);
}
}
?>
test.php
<?php
require 'my_exec.php';
my_background_exec('A::jack', array('hello', 'jack'), 'require "my_exec.php";require "a_class.php";', 8);
?>
你可以使用 set_time_limit(0); 让脚本永远运行 - 但是不建议这样做,你的 Web 服务器可能会因为设置的 HTTP 超时时间而阻止你(通常约 5 分钟)。
你应该查看你的 Web 服务器指南以了解更多关于 HTTP 超时时间的相关信息。
Jonathon
我在应用中遇到了脚本超时的问题,用户在其中触发了长时间运行的后台操作。我编写了这个 cURL/CLI 后台脚本,从 HTTP 发出请求时解决了这个问题。
<?php
/* BACKGROUND CLI 1.0
eric pecoraro _at_ shepard dot com - 2005-06-02
使用风险自负。不提供任何明示或暗示的担保。
将此文件包含在任何脚本的顶部,以在后台运行,不受时间限制... 例如,include('background_cli.php');
调用此文件的脚本不应该向浏览器返回输出。
*/
# 需求 - cURL 和 CLI
if ( !function_exists('curl_setopt') OR !function_exists('curl_setopt') ) {
echo '需要安装 cURL 和 CLI。' ; exit ;
}
# 构建路径
$script = array_pop(explode('/',$SCRIPT_NAME)) ;
$script_dir = substr($SCRIPT_NAME,0,strlen($SCRIPT_NAME)-strlen($script)) ;
$scriptURL = 'http://'. $HTTP_HOST . $script_dir . "$script" ;
$curlURL = 'http://'. $HTTP_HOST . $script_dir . "$script?runscript=curl" ;
# 指示脚本是否由 CLI 调用
if ( php_sapi_name() == 'cli' ) {
$CLI = true ;
}
# 如果脚本由 cURL_prompt() 调用,则执行的操作
if ( $runscript == 'curl' ) {
$cmd = "/usr/local/bin/php ".$PATH_TRANSLATED ; // 要运行的脚本的服务器位置
exec($cmd) ;
exit;
}
# 用户界面
// 提交后用户的答案。
if ( $post ) {
cURL_prompt($curlURL) ;
echo '<div style="margin:25px;"><title>后台 CLI</title>';
echo '好的。如果一切顺利,<b>'.$script.'</b> 正在后台努力工作,不受 ' ;
echo '超时限制。 <br><br><form action='.$scriptURL.' method=GET>' ;
echo '<input type=submit value=" 重置后台 CLI "></form></div>' ;
exit ;
}
// 启动屏幕。
if ( !$CLI AND !$runscript ) {
echo '<title>后台 CLI</title><div style="margin:25px;">' ;
echo '<form action='.$scriptURL.' method=POST>' ;
echo '点击从 PHP CLI 命令行在后台运行 <b>'.$script.'</b>。<br><br>' ;
echo '<input type=hidden value=1 name=post>' ;
echo '<input type=submit value=" 在后台运行 "></form></div>' ;
exit ;
}
# cURL URL 提示功能
function cURL_prompt($url_path) {
ob_start(); // 开始输出缓冲区
$c=curl_init($url_path);
curl_setopt($c, CURLOPT_TIMEOUT, 2); // 2 秒后断开连接
curl_exec($c);
curl_close($c);
ob_end_clean(); // 丢弃输出缓冲区
}
?>
文档说明
当被调用时,set_time_limit() 会将超时计数器从零重新开始。换句话说,如果超时时间是默认的 30 秒,并且在脚本执行了 25 秒后,执行了类似 set_time_limit(20) 的调用,那么脚本将在总共 45 秒后超时。
如果我有一个长时间运行的脚本,并且想要一个确切的时间限制,我会尽可能靠近第一行设置它。
您可能还需要查看 Apache 的超时设置(我使用的是 Win32 版本),我在 php.ini 中更改了最大执行时间值,但仍然被 httpd.conf 文件中的 Apache 超时值停止了。
set_tme_limit 重置执行时间计数。
测试代码 1
<?php
echo '<html><body>';
set_time_limit(1);
$i = 0;
while(++$i < 100000001){
if($i % 100000 == 0){
echo $i / 100000, "<br/>\n";
}
}
echo "done.<br/>\n";
// 不会回显 'done.'。
?>
测试代码 2
<?php
echo '<html><body>';
set_time_limit(1);
$i = 0;
while(++$i < 100000001){
if($i % 100000 == 0){
set_time_limit(1);
echo $i / 100000, "<br/>\n";
}
}
echo "done.<br/>\n";
// 会回显 'done.'
?>
这个工作可以对 HTML 流进行精细控制,并且可以超过时间限制。
<?php
header('Content-type: text/plain');
echo date("H:m:s"), "\n";
set_time_limit(30);
for ($i = 0; $i < 1000; $i++)
{
echo date("H:m:s"),"\n";
for ($r = 0; $r < 100000; $r++){
$X.= tan(M_LNPI+log(ceil( date("s")*M_PI*M_LNPI+100)));
}
ob_flush();
flush();
}
echo "work! $x";
?>
要找出当前设置的时间限制,请使用
<?php
ini_get('max_execution_time');
?>
如果之前在脚本中调用过 set_time_limit,则结果将是传递给 set_time_limit 的值(而不是像函数名 "ini_get" 所暗示的那样,来自 php.ini 文件的值)。
我希望能早点发现的一件事是,如果您正在使用 php-cli 并且确实需要限制执行时间,并且您在 *nix 中,可以使用 "timeout",它是 coreutils 的一部分。
例如
timeout 5 /usr/bin/php -q /path/to/script
如果它花费的时间超过 5 秒,它将被终止。
我写了一些用于 cacti 的快速 php 脚本。
当您使用 IIS 时,PHP 超时仅在它低于 IIS 定义的脚本超时时有效。
IIS 5 的默认超时时间为 300 秒。如果您需要更长的超时时间,您也必须更改 IIS 属性。否则,您的服务器将在 PHP 脚本达到其自己的超时时间之前停止它。
如果您将秒数设置为一个非常大的数字(没有多少人这样做,但以防万一),那么 php 将退出并出现致命错误,例如
Fatal error: Maximum execution time of 1 second exceeded in /path/to/your/script/why.php
[由 danbrown AT php DOT net 编辑:这是由于 32 位有符号整数的限制。]
如果您想计算脚本的墙上时间(包括所有外部 DB/HTTP 调用等)在 Unix 中(在 Windows 中,这已经是默认行为),您可以使用以下函数
<?php
$timeoutInSeconds = 3;
// 这将确保这始终被异步调用
pcntl_async_signals(1);
// 第二个参数是任何可调用函数(https://php.net/manual/en/language.types.callable.php)
pcntl_signal(SIGALRM, function() {
exit('Stop it!');
});
pcntl_alarm($timeoutInSeconds);
?>
一个很好的解决方法,可以获得一个真正的 max_execution_time(需要 posix 和 pcntl)
<?php
$pid=pcntl_fork();
if ($pid) {
// 长时间运行的进程
$a=0;
while (true) {
echo "a=$a\n\n";
ob_flush();
flush();
$a++;
shell_exec('sleep 10&');
}
} else {
// 时间限制检查器
sleep(5);
posix_kill(posix_getppid(),SIGKILL);
}
?>
如果您想检查还有多少时间剩余,这应该可以工作(至少在 Windows 上,在非 Windows 平台上,我不确定)
$seconds_remaining_until_termination = ini_get('max_execution_time') === "0" ? null : ((int)ini_get('max_execution_time'))-(microtime(true)-$_SERVER['REQUEST_TIME_FLOAT']);
它为您提供脚本因时间限制而被终止之前的秒数。(在 Windows 7 X64 SP1 上使用 PHP 7.3.7 测试) - 或者如果没有任何时间限制,则返回 null。
不幸的是,陷入无限循环的脚本可以在几秒钟内产生大量的输出。我试图调试一个脚本,我添加了
<?php
set_time_limit(2);
?>
到脚本的开头。不幸的是,即使是两秒钟的运行时间也产生了足够的输出来超载我的浏览器可用的内存。
所以,我写了一个简短的例程,它会限制执行时间,并限制返回的输出量。我将其添加到脚本的开头,它完美地工作了
<?php
set_time_limit(2);
ob_start(); // 缓冲输出
function shutdown () {
// 仅打印前 2000 个字符的输出
$out = ob_get_clean();
print substr($out, 0, 2000);
}
register_shutdown_function('shutdown');
?>
Windows 上 IIS 中五分钟后的超时是由继承的 CGI 超时值 300 秒引起的。这不是 PHP 问题。解决办法是为需要更长时间运行的文件或目录添加自定义值。
在 IIS 5.0 或 7.0(截至本文为 beta 版)中,您可以在相当细粒度的级别使用 IIS 管理器更改此值,位于(大致) YOURSITE -> 属性 -> 主目录 -> 配置(按钮) -> 选项,但在 IIS 6.0 中,此功能被关闭(!),因此您必须进入 Metabase。
在 Metabase Explorer 中查找站点编号(例如,12345678),然后从 CMD 提示符
[转到脚本目录]
cd C:\Inetpub\AdminScripts
[对于从站点根目录开始的每个子目录]
cscript adsutil.vbs CREATE W3SVC/12345678/root/"MY SUBDIRECTORY" IIsWebDirectory
[对于相关文件]
cscript adsutil.vbs CREATE W3SVC/12345678/root/"MY SUBDIRECTORY"/ILikeToTimeOut.php IIsWebFile
[设置超时]
cscript adsutil.vbs set W3SVC/12345678/root/"MY SUBDIRECTORY"/ILikeToTimeOut.php/CGITimeout "7200"
注意:"7200" 是以秒为单位的 2 小时,但可以是任何值。
我从这篇文章中推导出上述解决方案
http://www.iis-resources.com/modules/AMS/article.php?
storyid=509&page=3
如果您遇到类似
msg: set_time_limit() [function.set-time-limit]: Cannot set time limit in safe mode
尝试这个
<?php
if( !ini_get('safe_mode') ){
set_time_limit(25);
}
?>
关于 "nytshadow" 所说,重要的是要意识到 max-execution-time 和 set_time_limit 函数测量的是 CPU 处理脚本的时间。如果脚本阻塞,例如:用于输入、选择、睡眠等,那么阻塞和返回之间的时间不会被测量。从命令行界面运行脚本时也是如此。因此,如果您用 PHP 编写的日志解析器正在尾部追加文件,那么该程序最终会失败。这取决于读取足够多的输入来进行 30 秒的处理需要多长时间。
如果您正在编写一个应该无限运行的命令行脚本,强烈建议将 max-execution-time 设置为 0(永远不要停止)。
如果您正在运行需要未知时间或永远运行的脚本,您可以使用
set_time_limit(0);
.....
...
..
.
并在脚本末尾使用 flush() 函数告诉 PHP 发送它已经生成的內容。
如果您使用的是 PHP_CLI SAPI 并收到错误 "Maximum execution time of N seconds exceeded"(其中 N 是一个整数值),请尝试每 M 秒或每次迭代调用 set_time_limit(0)。例如
<?php
require_once('db.php');
$stmt = $db->query($sql);
while ($row = $stmt->fetchRow()) {
set_time_limit(0);
// 您的代码在这里
}
?>