设置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上并非如此,在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
function 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);
}
function 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与父进程的进程组ID分离
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信号(负数表示进程组ID,所有子进程共享顶级进程组,除了嵌套的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
class A
{
static function 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
/* 后台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.'
?>
要查找当前设置的超时限制,请使用
<?php
ini_get('max_execution_time');
?>
如果之前在脚本中调用过 set_time_limit,则结果将是传递给 set_time_limit 的值(而不是像函数名“ini_get”似乎表明的那样,来自 php.ini 文件的值)。
这可以很好地工作,实现了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";
?>
当您使用 IIS 时,PHP 超时只有在它低于 IIS 定义的脚本超时时才有效。
IIS 5 的默认超时时间为 300 秒。如果您需要更长的超时时间,则还必须更改 IIS 属性。否则,您的服务器将在 PHP 脚本达到其自身的超时时间之前停止它。
我希望早点发现的一件事是,如果您使用的是 php-cli 并确实需要限制执行时间,并且如果您在 *nix 系统中,您可以使用 coreutils 中的“timeout”。
例如
timeout 5 /usr/bin/php -q /path/to/script
如果它花费的时间超过 5 秒,它将终止脚本。
我编写了一些快速 PHP 脚本,例如与 cacti 一起使用。
如果您想检查剩余时间,这应该有效(至少在 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。
如果您想计算脚本的实际运行时间(包括所有外部数据库/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);
?>