While building a page to serve back to the user, I'd like to submit a background script to do some numerical analysis of data in the database and send the result to the user in an email. This process may take a minute or so, so I don't want to delay the page serve while it runs.

Is there a way to trigger another PHP script from the script that's building the page so it can send the page and be done while the other script runs in the background?

For testing, this TEST.PHP:

<?php
set_time_limit(0);
mail ('myemail@myemail.com','Test Email from ClockPie', 'foobar');
?>

Then I put this in the script that builds the page serve:

...
shell_exec ('test.php'); 
...

I'm running under Windoze 7 Home Premium. Is there something obviously wrong with this?

And yes, I know this is essentially a duplicate question and there are other existing questions about this same thing, but I'm too much of a peon here on StackOverflow to simply add comments and participate in the discussions :-(

有帮助吗?

解决方案

If you want to fork another PHP process, you would need to use pcntl_fork. However I don't think that this is the best method here. Instead I would make the script accessed by the browser add the data to be processed to a queue. Then set up a scheduled task to run every 'x' number of minutes that will process the queue and email the user when done.

I am assuming, of course, that you will make this scheduled script to be extremely lightweight if there is actually nothing in the queue (i.e. it should take a few milliseconds to complete). If not, you will be bogging down your server every few minutes and in that case looking into something like pcntl_fork would be the better solution.

One disadvantage of doing it this way is if you set it to run, for example, every 5 minutes, but it only takes 2 minutes to process the data, the user would be required to wait up to 3 extra minutes to receive the email. If this is a problem, tweak it to run more often.

其他提示

shell_exec() is just like sitting at a command prompt and typing the string value and pressing return. You can't just type "test.php" and have it fire, you need to run PHP, and point it to your php file.

Depending on how you have php installed, you maybe able to simple change it to shell_exec('php test.php'); or you may have to provide a path to PHP like shell_exec('C:\php\bin\php.exe test.php');. The path will depend on your environment.

In the code you show, if you're just sending a single email, that shouldn't take more than a few seconds so I wouldn't even go down this route. But I don't think this is the end result of your code, and just a sample.

What you can do is output the page and flush() it so all data gets sent to the user. Then you sent the email. The user will see the page, but in the background it's still loading. This doesn't require anything like shell_exec();

This method is used by software like phpBB too to handle cron jobs taking quite some time.

flush() documentation: http://php.net/manual/en/function.flush.php

Basic Program Architecture:

  1. Build & Echo Page
  2. flush()
  3. Send Email

You can use shell_exec() to run your 'sub php script' asynchronously, meaning that it will run in the background while your main php script continues, by appending the command that fires-off your 'sub php script' with the & symbol. So, it would be something like this:

$cmd="php /path/to/you/php/sub/script.php &";
shell_exec($cmd);
//main script continues running here, while sub script runs in the background...

The following code works on my system. Note that I'm just a hobbyist, not an expert, so this might not fall under the category of 'best practices', and I have no idea what the security implications might be, but this absolutely works, creating multiple threads that all run concurrently. Never mind about the folder name 'Calories'. That just happens to be the folder I was working in when I threw together this example code.

main.php:

error_log('Hello, world, from main!');

$numberOfThreadsToCreate = 3;

for($i = 0; $i < $numberOfThreadsToCreate; ++$i) {
    error_log("Main starting child {$i}");

    $fp = fsockopen('localhost', 8888);
    if(!$fp) {
        error_log("$errstr ($errno)");
        exit;
    }

    $firstSleep = $numberOfThreadsToCreate - $i;
    $header = "GET /Calories/thread.php?threadID={$i}&firstSleep={$firstSleep}"
                . " HTTP/1.1\r\n"
                . "Host: localhost\r\n"
                . "Connection: Close\r\n\r\n";

    $r = fputs($fp, $header); 
    fclose($fp);

    sleep(1);
}

for($i = 0; $i < 5; ++$i) {
    sleep(1);
    error_log('Main is still running');
}

error_log("Goodbye, cruel world, from main!");


thread.php

$myThreadID = $_GET['threadID'];
$sleep = $_GET['firstSleep'];

error_log("Hello, world, from child thread, ID={$myThreadID}!");
for($i = 0; $i < 5; ++$i) {
    error_log("Child {$myThreadID} sleeping for {$sleep} seconds");
    sleep($sleep);
    $sleep = 1;
}

error_log("Goodbye, cruel world, from child thread, ID={$myThreadID}!");

And the logfile results:

Hello, world, from main!
Main starting child 0
Hello, world, from child thread, ID=0!
Child 0 sleeping for 3 seconds
Main starting child 1
Hello, world, from child thread, ID=1!
Child 1 sleeping for 2 seconds
Main starting child 2
Hello, world, from child thread, ID=2!
Child 2 sleeping for 1 seconds
Child 1 sleeping for 1 seconds
Child 2 sleeping for 1 seconds
Child 0 sleeping for 1 seconds
Child 1 sleeping for 1 seconds
Child 2 sleeping for 1 seconds
Child 0 sleeping for 1 seconds
Main is still running
Child 1 sleeping for 1 seconds
Child 2 sleeping for 1 seconds
Child 0 sleeping for 1 seconds
Main is still running
Child 1 sleeping for 1 seconds
Child 2 sleeping for 1 seconds
Child 0 sleeping for 1 seconds
Main is still running
Goodbye, cruel world, from child thread, ID=1!
Goodbye, cruel world, from child thread, ID=2!
Main is still running
Goodbye, cruel world, from child thread, ID=0!
Main is still running
Goodbye, cruel world, from main!
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top