php esegue un processo in background


259

Devo eseguire una copia della directory su un'azione dell'utente, ma le directory sono piuttosto grandi, quindi vorrei essere in grado di eseguire tale azione senza che l'utente sia consapevole del tempo impiegato per il completamento della copia.

Qualsiasi suggerimento sarebbe molto apprezzato.


3
Questo stackoverflow.com/questions/5367261/… spiega come farlo sotto Windows
shealtiel

Risposte:


365

Supponendo che questo sia in esecuzione su una macchina Linux, l'ho sempre gestito in questo modo:

exec(sprintf("%s > %s 2>&1 & echo $! >> %s", $cmd, $outputfile, $pidfile));

Questo avvia il comando $cmd, reindirizza l'output del comando $outputfilee scrive l'id del processo su $pidfile.

Ciò ti consente di monitorare facilmente cosa sta facendo il processo e se è ancora in esecuzione.

function isRunning($pid){
    try{
        $result = shell_exec(sprintf("ps %d", $pid));
        if( count(preg_split("/\n/", $result)) > 2){
            return true;
        }
    }catch(Exception $e){}

    return false;
}

2
Il programma di installazione di sudo deve essere eseguito senza richiedere una password? Tutti i comandi che richiedono l'input dell'utente non funzioneranno.
Mark Biek,

17
Questo stackoverflow.com/questions/5367261/… spiega come fare lo stesso sotto windows
shealtiel

13
L'ho usato, ma l'ho leggermente aggiornato, quindi non ho dovuto scrivere il PID in un file. Quindi uso questo formato: <code> exec (sprintf ("$ s> $ s 2> & 1 & echo $ 1", $ cmd, $ outputfile), $ pidArr); </code> Il PID del processo risultante è in $ pidArr [0]
Kaiesh

3
@Kaiesh: dovrebbe essere "echo $!"
brunobg,

8
Kaiesh ha scritto un errore di battitura, '$' invece di '%'. Versione corretta: exec (sprintf ("% s>% s 2> & 1 & echo $!", $ Cmd, $ outputfile), $ pidArr)
Yuri Gor

23

Scrivi il processo come uno script lato server in qualunque lingua (php / bash / perl / etc) sia utile e poi chiamalo dalle funzioni di controllo del processo nel tuo script php.

La funzione probabilmente rileva se lo io standard viene utilizzato come flusso di output e se è allora che imposterà il valore di ritorno ... se non poi finisce

proc_close( proc_open( "./command --foo=1 &", array(), $foo ) );

L'ho provato rapidamente dalla riga di comando usando "sleep 25s" come comando e ha funzionato come un incantesimo.

( Risposta trovata qui )


3
Questo è l'unico modo in cui sono riuscito a farlo funzionare su Centos. Grazie!
Deac Karns,

Grazie ! Vi è un'incompatibilità tra PHP e BASH. Se si esegue un sistema più recente, l'avvio non verrà eseguito in background utilizzando system () o exec (). I descrittori di file sembrano bloccarsi anche se si reindirizza. Il tuo metodo ha funzionato. un'altra alternativa è usare un vecchio binario bash per PHP, ma è pazzesco
John,

22

Potresti provare ad aggiungere questo al tuo comando

>/dev/null 2>/dev/null &

per esempio.

shell_exec('service named reload >/dev/null 2>/dev/null &');

Intendevo> / dev / null 2> / dev / null &
wlf

Per me funziona, e userò il makefile per fare qualcosa sul sito del server, grazie! (es. make, make restart)
Chu-Siang Lai,

Questo è molto più semplice della risposta accettata! (Finché non hai bisogno di qualcosa di più complesso, come verificare se il processo è ancora in esecuzione.)
Sean the Bean

18

Vorrei solo aggiungere un esempio molto semplice per testare questa funzionalità su Windows:

Creare i seguenti due file e salvarli in una directory Web:

foreground.php:

<?php

ini_set("display_errors",1);
error_reporting(E_ALL);

echo "<pre>loading page</pre>";

function run_background_process()
{
    file_put_contents("testprocesses.php","foreground start time = " . time() . "\n");
    echo "<pre>  foreground start time = " . time() . "</pre>";

    // output from the command must be redirected to a file or another output stream 
    // http://ca.php.net/manual/en/function.exec.php

    exec("php background.php > testoutput.php 2>&1 & echo $!", $output);

    echo "<pre>  foreground end time = " . time() . "</pre>";
    file_put_contents("testprocesses.php","foreground end time = " . time() . "\n", FILE_APPEND);
    return $output;
}

echo "<pre>calling run_background_process</pre>";

$output = run_background_process();

echo "<pre>output = "; print_r($output); echo "</pre>";
echo "<pre>end of page</pre>";
?>

background.php:

<?
file_put_contents("testprocesses.php","background start time = " . time() . "\n", FILE_APPEND);
sleep(10);
file_put_contents("testprocesses.php","background end time = " . time() . "\n", FILE_APPEND);
?>

Autorizza IUSR a scrivere nella directory in cui hai creato i file sopra

Concedere l'autorizzazione IUSR a LEGGERE ed ESEGUIRE C: \ Windows \ System32 \ cmd.exe

Premi in primo piano.php da un browser web

È necessario eseguire il rendering nel browser con i timestamp correnti e la risorsa locale n. Nell'array di output:

loading page
calling run_background_process
  foreground start time = 1266003600
  foreground end time = 1266003600
output = Array
(
    [0] => 15010
)
end of page

Dovresti vedere testoutput.php nella stessa directory in cui sono stati salvati i file sopra, e dovrebbe essere vuoto

Dovresti vedere testprocesses.php nella stessa directory in cui sono stati salvati i file precedenti e dovrebbe contenere il seguente testo con i timestamp correnti:

foreground start time = 1266003600
foreground end time = 1266003600
background start time = 1266003600
background end time = 1266003610

10

Se devi semplicemente fare qualcosa in background senza che la pagina PHP sia in attesa che venga completata, puoi usare un altro script PHP (in background) che viene "invocato" con il comando wget. Questo script PHP in background verrà eseguito con privilegi, ovviamente, come qualsiasi altro script PHP sul tuo sistema.

Ecco un esempio su Windows usando wget dai pacchetti gnuwin32.

Il codice di sfondo (file test-proc-bg.php) come esempio ...

sleep(5);   // some delay
file_put_contents('test.txt', date('Y-m-d/H:i:s.u')); // writes time in a file

Lo script in primo piano, quello che invoca ...

$proc_command = "wget.exe http://localhost/test-proc-bg.php -q -O - -b";
$proc = popen($proc_command, "r");
pclose($proc);

È necessario utilizzare popen / pclose affinché funzioni correttamente.

Le opzioni di wget:

-q    keeps wget quiet.
-O -  outputs to stdout.
-b    works on background

7

Ecco una funzione per avviare un processo in background in PHP. Finalmente ne ho creato uno che funziona anche su Windows, dopo aver letto e testato diversi approcci e parametri.

function LaunchBackgroundProcess($command){
  // Run command Asynchroniously (in a separate thread)
  if(PHP_OS=='WINNT' || PHP_OS=='WIN32' || PHP_OS=='Windows'){
    // Windows
    $command = 'start "" '. $command;
  } else {
    // Linux/UNIX
    $command = $command .' /dev/null &';
  }
  $handle = popen($command, 'r');
  if($handle!==false){
    pclose($handle);
    return true;
  } else {
    return false;
  }
}

Nota 1: su Windows, non utilizzare il /Bparametro come suggerito altrove. Impone al processo di eseguire la stessa finestra della console del startcomando stesso, determinando l'elaborazione sincronizzata del processo. Per eseguire il processo in un thread separato (in modo asincrono), non utilizzare /B.

Nota 2: Le virgolette doppie vuote dopo start ""sono necessarie se il comando è un percorso tra virgolette. startIl comando interpreta il primo parametro citato come titolo della finestra.


6

Bene, ho trovato una versione un po 'più veloce e più facile da usare

shell_exec('screen -dmS $name_of_screen $command'); 

e funziona.


@ Alpha.Dev L'ho testato solo su Linux Non so se funzionerà su un altro sistema.
Morfidon,

4

Puoi organizzare il fork di un processo separato e quindi eseguire la tua copia in background? È da un po 'che non faccio PHP, ma la funzione pcntl-fork sembra promettente.


Questo non fornisce una risposta alla domanda. Per criticare o richiedere chiarimenti a un autore, lascia un commento sotto il suo post.
Rizier123,

13
Grazie - I commenti non esistevano nel '08, ma lo terrò a mente :)
Matt Sheppard

4

Utilizzare questa funzione per eseguire il programma in background. È multipiattaforma e completamente personalizzabile.

<?php
function startBackgroundProcess(
    $command,
    $stdin = null,
    $redirectStdout = null,
    $redirectStderr = null,
    $cwd = null,
    $env = null,
    $other_options = null
) {
    $descriptorspec = array(
        1 => is_string($redirectStdout) ? array('file', $redirectStdout, 'w') : array('pipe', 'w'),
        2 => is_string($redirectStderr) ? array('file', $redirectStderr, 'w') : array('pipe', 'w'),
    );
    if (is_string($stdin)) {
        $descriptorspec[0] = array('pipe', 'r');
    }
    $proc = proc_open($command, $descriptorspec, $pipes, $cwd, $env, $other_options);
    if (!is_resource($proc)) {
        throw new \Exception("Failed to start background process by command: $command");
    }
    if (is_string($stdin)) {
        fwrite($pipes[0], $stdin);
        fclose($pipes[0]);
    }
    if (!is_string($redirectStdout)) {
        fclose($pipes[1]);
    }
    if (!is_string($redirectStderr)) {
        fclose($pipes[2]);
    }
    return $proc;
}

Si noti che dopo l'avvio del comando, per impostazione predefinita questa funzione chiude lo stdin e lo stdout del processo in esecuzione. È possibile reindirizzare l'output del processo in alcuni file tramite gli argomenti $ redirectStdout e $ redirectStderr.

Nota per gli utenti di Windows:
non è possibile reindirizzare stdout / stderr nulnel modo seguente:

startBackgroundProcess('ping yandex.com', null, 'nul', 'nul');

Tuttavia, puoi farlo:

startBackgroundProcess('ping yandex.com >nul 2>&1');

Note per gli utenti * nix:

1) Utilizzare il comando exec shell se si desidera ottenere il PID effettivo:

$proc = startBackgroundProcess('exec ping yandex.com -c 15', null, '/dev/null', '/dev/null');
print_r(proc_get_status($proc));

2) Utilizzare l'argomento $ stdin se si desidera passare alcuni dati all'input del programma:

startBackgroundProcess('cat > input.txt', "Hello world!\n");

3

Potresti provare un sistema di accodamento come Resque . È quindi possibile generare un lavoro, che elabora le informazioni e ritorna abbastanza velocemente con l'immagine di "elaborazione". Con questo approccio non saprai quando sarà finito.

Questa soluzione è progettata per applicazioni su larga scala, in cui non si desidera che le macchine frontali eseguano lavori pesanti, in modo che possano elaborare le richieste degli utenti. Pertanto potrebbe funzionare o meno con dati fisici come file e cartelle, ma per l'elaborazione di logiche più complicate o altre attività asincrone (ovvero nuove mail di registrazioni) è bello avere e molto scalabile.


2

Una soluzione funzionante per Windows e Linux. Scopri di più sulla pagina My github .

function run_process($cmd,$outputFile = '/dev/null', $append = false){
                    $pid=0;
                if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                        $cmd = 'wmic process call create "'.$cmd.'" | find "ProcessId"';
                        $handle = popen("start /B ". $cmd, "r");
                        $read = fread($handle, 200); //Read the output 
                        $pid=substr($read,strpos($read,'=')+1);
                        $pid=substr($pid,0,strpos($pid,';') );
                        $pid = (int)$pid;
                        pclose($handle); //Close
                }else{
                    $pid = (int)shell_exec(sprintf('%s %s %s 2>&1 & echo $!', $cmd, ($append) ? '>>' : '>', $outputFile));
                }
                    return $pid;
            }
            function is_process_running($pid){
                if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                        //tasklist /FI "PID eq 6480"
                    $result = shell_exec('tasklist /FI "PID eq '.$pid.'"' );
                    if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result)) {
                        return true;
                    }
                }else{
                    $result = shell_exec(sprintf('ps %d 2>&1', $pid));
                    if (count(preg_split("/\n/", $result)) > 2 && !preg_match('/ERROR: Process ID out of range/', $result)) {
                        return true;
                    }
                }
                return false;
            }
            function stop_process($pid){
                    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                            $result = shell_exec('taskkill /PID '.$pid );
                        if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result)) {
                            return true;
                        }
                    }else{
                            $result = shell_exec(sprintf('kill %d 2>&1', $pid));
                        if (!preg_match('/No such process/', $result)) {
                            return true;
                        }
                    }
            }


1

Invece di avviare un processo in background, che dire della creazione di un file trigger e di avere uno scheduler come cron o autosys che esegue periodicamente uno script che cerca e agisce sui file trigger? I trigger potrebbero contenere istruzioni o persino comandi non elaborati (meglio ancora, basta renderlo uno script di shell).



1

Sto usando pesantemente fast_cgi_finish_request()

In combinazione con una chiusura e register_shutdown_function ()

$message ='job executed';
$backgroundJob = function() use ($message) {
     //do some work here
    echo $message;
}

Quindi registrare questa chiusura da eseguire prima dell'arresto.

register_shutdown_function($backgroundJob);

Alla fine, quando la risposta è stata inviata al client, puoi chiudere la connessione al client e continuare a lavorare con il processo PHP:

fast_cgi_finish_request();

La chiusura verrà eseguita dopo fast_cgi_finish_request.

Il messaggio $ non sarà visibile in nessun momento. E puoi registrare tutte le chiusure che desideri, ma fai attenzione ai tempi di esecuzione degli script. Funzionerà solo se PHP è in esecuzione come modulo Fast CGI (giusto ?!)


0

Lo scripting PHP non è come il linguaggio di sviluppo di altre applicazioni desktop. Nei linguaggi delle applicazioni desktop possiamo impostare thread daemon per eseguire un processo in background ma in PHP si verifica un processo quando la richiesta dell'utente per una pagina. Tuttavia, è possibile impostare un lavoro in background utilizzando la funzionalità del lavoro cron del server che lo script php esegue.


0

Per quelli di noi che usano Windows , guarda questo:

Riferimento: http://php.net/manual/en/function.exec.php#43917

Ho anche lottato per far funzionare un programma in background in Windows mentre lo script continua a essere eseguito. Questo metodo, a differenza delle altre soluzioni, consente di avviare qualsiasi programma ridotto a icona, ingrandito o senza alcuna finestra. La soluzione di llbra @ phpbrasil funziona ma a volte produce una finestra indesiderata sul desktop quando si desidera davvero che l'attività venga eseguita nascosta.

avviare Notepad.exe minimizzato in background:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("notepad.exe", 7, false); 
?> 

avviare un comando shell invisibile in background:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("cmd /C dir /S %windir%", 0, false); 
?> 

avvia MSPaint ingrandito e attendi che tu lo chiuda prima di continuare lo script:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("mspaint.exe", 3, true); 
?> 

Per ulteriori informazioni sul metodo Run (), visitare: http://msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp

URL modificato:

Vai invece a https://technet.microsoft.com/en-us/library/ee156605.aspx poiché il link sopra non esiste più.


1
Cosa bisogna fare per eseguire questo codice? RicevoFatal error: Class 'COM' not found
Alph.Dev il

Ps Il collegamento a MSDN è interrotto
Alph.Dev

@ Alph.Dev: il documento msdn sembra essere stato temporaneamente rimosso. Contiene ulteriori esempi e spiegazioni su come usare WScript.Shell.
Jimmy Ilenloa,

@ Alph.Dev: per la classe COM non trovata, questa è un'altra cosa del tutto. Dai un'occhiata a google. Si potrebbe anche verificare questo php.net/manual/en/com.installation.php
Jimmy Ilenloa

@ Alph.Dev, consultare technet.microsoft.com/en-us/library/ee156605.aspx per informazioni aggiornate sulla funzione Run ()
Jimmy Ilenloa,

-12

So che è un post di 100 anni, ma comunque, ho pensato che potesse essere utile a qualcuno. Puoi mettere un'immagine invisibile da qualche parte nella pagina che punta all'URL che deve essere eseguito in background, in questo modo:

<img src="run-in-background.php" border="0" alt="" width="1" height="1" />


12
Soluzione davvero negativa. Se l'utente lascia la pagina, il processo verrà interrotto.
Sergey P. aka azzurro

3
l '"immagine invisibile" e il suo collegamento sono esposti nel codice sorgente della pagina.
tony gil,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.