如果在PHP中花费的时间过长,有没有办法可以中止一段代码?也许是这样的事情:
//Set the max time to 2 seconds
$time = new TimeOut(2);
$time->startTime();
sleep(3)
$time->endTime();
if ($time->timeExpired()){
echo 'This function took too long to execute and was aborted.';
}
它不一定完全像上面那样,但有没有任何本机PHP函数或类可以做这样的事情?
编辑:Ben Lee对pcnt_fork
的回答将是完美的解决方案,除非它不适用于Windows。有没有其他方法可以使用适用于Windows和Linux的PHP,但不需要外部库?
编辑2 :XzKto的解决方案在某些情况下有效,但不一致,无论我尝试什么,我都无法捕捉到异常。用例是检测单元测试的超时。如果测试超时,我想终止它,然后继续进行下一次测试。
答案 0 :(得分:10)
您可以通过分叉进程,然后使用父进程监视子进程来执行此操作。 pcntl_fork是一种分叉进程的方法,因此在内存中并行运行两个几乎完全相同的程序。唯一的区别是,在一个进程中,父进程pcntl_fork
返回一个正整数,该整数对应于子进程的进程ID。在另一个过程中,孩子pcntl_fork
返回0。
以下是一个例子:
$pid = pcntl_fork();
if ($pid == 0) {
// this is the child process
} else {
// this is the parent process, and we know the child process id is in $pid
}
这是基本结构。下一步是添加进程到期。您的东西将在子进程中运行,父进程将仅负责监视子进程并对其进行计时。但是为了让一个进程(父进程)杀死另一个进程(子进程),需要有一个信号。信号是进程通信的方式,意味着“你应该立即结束”的信号是SIGKILL。您可以使用posix_kill发送此信号。所以父母应该等待2秒然后杀死孩子,如下:
$pid = pcntl_fork();
if ($pid == 0) {
// this is the child process
// run your potentially time-consuming method
} else {
// this is the parent process, and we know the child process id is in $pid
sleep(2); // wait 2 seconds
posix_kill($pid, SIGKILL); // then kill the child
}
答案 1 :(得分:8)
如果你在一个命令(例如sleep())上编写脚本暂停,除了分叉之外,你不能真的这样做,但是对于特殊情况有很多解决方法:如果你编程暂停数据库查询,就像异步查询一样proc_open如果你的程序暂停一些外部执行等等。不幸的是它们都是不同的,所以没有通用的解决方案。
如果脚本等待一个很长的循环/多行代码,你可以做一个像这样的脏技巧:
declare(ticks=1);
class Timouter {
private static $start_time = false,
$timeout;
public static function start($timeout) {
self::$start_time = microtime(true);
self::$timeout = (float) $timeout;
register_tick_function(array('Timouter', 'tick'));
}
public static function end() {
unregister_tick_function(array('Timouter', 'tick'));
}
public static function tick() {
if ((microtime(true) - self::$start_time) > self::$timeout)
throw new Exception;
}
}
//Main code
try {
//Start timeout
Timouter::start(3);
//Some long code to execute that you want to set timeout for.
while (1);
} catch (Exception $e) {
Timouter::end();
echo "Timeouted!";
}
但我认为这不是很好。如果您指定确切的案例我认为我们可以帮助您更好。
答案 2 :(得分:1)
如果执行时间超出限制,您可以使用declare函数。 http://www.php.net/manual/en/control-structures.declare.php
这是一个如何使用
的代码示例define("MAX_EXECUTION_TIME", 2); # seconds
$timeline = time() + MAX_EXECUTION_TIME;
function check_timeout()
{
if( time() < $GLOBALS['timeline'] ) return;
# timeout reached:
print "Timeout!".PHP_EOL;
exit;
}
register_tick_function("check_timeout");
$data = "";
declare( ticks=1 ){
# here the process that might require long execution time
sleep(5); // Comment this line to see this data text
$data = "Long process result".PHP_EOL;
}
# Ok, process completed, output the result:
print $data;
使用此代码,您将看到超时消息。 如果要在声明块中获取Long处理结果,可以删除sleep(5)行或增加脚本开头声明的最大执行时间
答案 3 :(得分:0)
如果您没有处于安全模式,那么set-time-limit会如何。
答案 4 :(得分:0)
在大约两分钟内完成了这项工作,我忘了给$time->startTime();
打电话,所以我真的不确切知道花了多长时间;)
class TimeOut{
public function __construct($time=0)
{
$this->limit = $time;
}
public function startTime()
{
$this->old = microtime(true);
}
public function checkTime()
{
$this->new = microtime(true);
}
public function timeExpired()
{
$this->checkTime();
return ($this->new - $this->old > $this->limit);
}
}
demo 。
我并没有真正了解您的endTime()
电话所做的事情,所以我改为checkTime()
,这也没有任何实际意义,只是为了更新内部价值。 timeExpired()
会自动调用它,因为如果您忘记拨打checkTime()
并使用过去时间,它肯定会发臭。
答案 5 :(得分:0)
这是一个老问题,现在可能已经解决了很多次,但是对于那些寻找解决这个问题的简单方法的人来说,现在有一个库:PHP Invoker。
答案 6 :(得分:0)
您还可以使用第二个脚本,其中包含暂停代码,该脚本通过具有超时设置的curl调用执行。另一个明显的解决方案是解决暂停的原因。
答案 7 :(得分:0)
这是我的方法。感谢其他人的答案:
<?php
class Timeouter
{
private static $start_time = FALSE, $timeout;
/**
* @param integer $seconds Time in seconds
* @param null $error_msg
*/
public static function limit($seconds, $error_msg = NULL)
: void
{
self::$start_time = microtime(TRUE);
self::$timeout = (float) $seconds;
register_tick_function([ self::class, 'tick' ], $error_msg);
}
public static function end()
: void
{
unregister_tick_function([ self::class, 'tick' ]);
}
public static function tick($error)
: void
{
if ((microtime(TRUE) - self::$start_time) > self::$timeout) {
throw new \RuntimeException($error ?? 'You code took too much time.');
}
}
public static function step()
: void
{
usleep(1);
}
}
然后您可以尝试这样:
<?php
try {
//Start timeout
Timeouter::limit(2, 'You code is heavy. Sorry.');
//Some long code to execute that you want to set timeout for.
declare(ticks=1) {
foreach (range(1, 100000) as $x) {
Timeouter::step(); // Not always necessary
echo $x . "-";
}
}
Timeouter::end();
} catch (Exception $e) {
Timeouter::end();
echo $e->getMessage(); // 'You code is heavy. Sorry.'
}
答案 8 :(得分:-2)
我使用pcntl_fork和lockfile在php中创建了一个脚本,以控制超时后执行kill的外部调用的执行。
#!/usr/bin/env php
<?php
if(count($argv)<4){
print "\n\n\n";
print "./fork.php PATH \"COMMAND\" TIMEOUT\n"; // TIMEOUT IN SECS
print "Example:\n";
print "./fork.php /root/ \"php run.php\" 20";
print "\n\n\n";
die;
}
$PATH = $argv[1];
$LOCKFILE = $argv[1].$argv[2].".lock";
$TIMEOUT = (int)$argv[3];
$RUN = $argv[2];
chdir($PATH);
$fp = fopen($LOCKFILE,"w");
if (!flock($fp, LOCK_EX | LOCK_NB)) {
print "Already Running\n";
exit();
}
$tasks = [
"kill",
"run",
];
function killChilds($pid,$signal) {
exec("ps -ef| awk '\$3 == '$pid' { print \$2 }'", $output, $ret);
if($ret) return 'you need ps, grep, and awk';
while(list(,$t) = each($output)) {
if ( $t != $pid && $t != posix_getpid()) {
posix_kill($t, $signal);
}
}
}
$pidmaster = getmypid();
print "Add PID: ".(string)$pidmaster." MASTER\n";
foreach ($tasks as $task) {
$pid = pcntl_fork();
$pidslave = posix_getpid();
if($pidslave != $pidmaster){
print "Add PID: ".(string)$pidslave." ".strtoupper($task)."\n";
}
if ($pid == -1) {
exit("Error forking...\n");
}
else if ($pid == 0) {
execute_task($task);
exit();
}
}
while(pcntl_waitpid(0, $status) != -1);
echo "Do stuff after all parallel execution is complete.\n";
unlink($LOCKFILE);
function execute_task($task_id) {
global $pidmaster;
global $TIMEOUT;
global $RUN;
if($task_id=='kill'){
print("SET TIMEOUT = ". (string)$TIMEOUT."\n");
sleep($TIMEOUT);
print("FINISHED BY TIMEOUT: ". (string)$TIMEOUT."\n");
killChilds($pidmaster,SIGTERM);
die;
}elseif($task_id=='run'){
###############################################
### START EXECUTION CODE OR EXTERNAL SCRIPT ###
###############################################
system($RUN);
################################
### END ###
################################
killChilds($pidmaster,SIGTERM);
die;
}
}
测试脚本run.php
<?php
$i=0;
while($i<25){
print "test... $i\n";
$i++;
sleep(1);
}