Asynchroniczne wykonanie powłoki w PHP

199

Mam skrypt PHP, który musi wywoływać skrypt powłoki, ale nie dba o wynik. Skrypt powłoki wykonuje wiele wywołań SOAP i jest powolny do zakończenia, więc nie chcę spowalniać żądania PHP podczas oczekiwania na odpowiedź. W rzeczywistości żądanie PHP powinno być w stanie wyjść bez przerywania procesu powłoki.

Szukałem w różnych exec(), shell_exec(), pcntl_fork(), itd funkcji, ale żaden z nich nie wydają się dokładnie to, co chcę zaoferować. (Lub, jeśli tak, to nie jest dla mnie jasne, jak.) Jakieś sugestie?

AdamTheHutt
źródło
Bez względu na to, jakie rozwiązanie wybierzesz, powinieneś również rozważyć użycie nicei ionicezapobieganie przytłaczaniu skryptu powłoki przez twój system (np. /usr/bin/ionice -c3 /usr/bin/nice -n19)
rinogo
Możliwy duplikat php wykonuje proces w tle
Sean the Bean

Odpowiedzi:

218

Jeśli „nie zależy na danych wyjściowych”, czy nie można wywołać exec do skryptu z &procesem w tle?

EDYCJA - dołączając to, co @ AdamTheHut skomentował do tego postu, możesz dodać to do zaproszenia do exec:

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

To przekieruje zarówno stdio(pierwszy >), jak i stderr( 2>) /dev/nulli uruchomi w tle.

Istnieją inne sposoby na zrobienie tego samego, ale jest to najłatwiejszy do odczytania.


Alternatywa dla powyższego podwójnego przekierowania:

" &> /dev/null &"
królikarnia
źródło
11
Wydaje się, że to działa, ale potrzebuje trochę więcej niż ampersand. Sprawiłem, że dołączyłem „> / dev / null 2> / dev / null &” do wywołania exec (). Chociaż muszę przyznać, że nie jestem do końca pewien, co to robi.
AdamTheHutt,
2
Zdecydowanie droga, jeśli chcesz strzelać i zapomnieć z php i apache. Wiele produkcyjnych środowisk Apache i PHP będzie miało wyłączoną funkcję pcntl_fork ().
metoda
9
Uwaga: &> /dev/null &xdebug nie wygeneruje dzienników, jeśli go użyjesz. Sprawdź stackoverflow.com/questions/4883171/…
kapeels
5
@MichaelJMulligan zamyka deskryptory plików. Mimo to, pomimo wzrostu wydajności, z perspektywy czasu /dev/nulllepszym rozwiązaniem jest używanie , ponieważ pisanie do zamkniętych FD powoduje błędy, podczas gdy próby czytania lub pisania /dev/nullpo prostu w milczeniu nic nie robią.
Charles Duffy
3
Skrypty w tle zostaną zabite podczas ponownego uruchamiania apache ... po prostu pamiętaj o tym przy bardzo długich zadaniach lub jeśli masz pecha jeśli chodzi o czas i zastanawiasz się, dlaczego twoja praca zniknęła ...
Julien
53

Kiedyś na do tego, jak to jest naprawdę zaczyna niezależnego procesu.

<?php
    `echo "the command"|at now`;
?>
Czimi
źródło
4
w niektórych sytuacjach jest to absolutnie najlepsze rozwiązanie. był to jedyny, który działał dla mnie, aby wydać „sudo restart” („echo” sleep 3; sudo restart ”| at now”) z webgui ORAZ zakończyć renderowanie strony .. na openbsd
Kaii
1
jeśli użytkownik, którego uruchamiasz Apache (zwykle www-data), nie ma uprawnień do używania ati nie możesz go skonfigurować, możesz spróbować użyć <?php exec('sudo sh -c "echo \"command\" | at now" ');Jeśli commandzawiera cudzysłowy, zobacz escapeshellarg, aby zaoszczędzić ci bólu głowy
Julien
1
Dobrze się nad tym zastanowić, aby sudo uruchomiło sh, nie jest najlepszym pomysłem, ponieważ zasadniczo zapewnia sudo dostęp do roota do wszystkiego. Wróciłem do używania echo "sudo command" | at nowi komentowania www-dataw/etc/at.deny
Julien
@Julien może mógłbyś utworzyć skrypt powłoki za pomocą polecenia sudo i zamiast tego uruchomić go. o ile nie przekazujesz żadnych wartości przesłanych przez użytkownika do wspomnianego skryptu
Garet Claborn,
26

Do wszystkich użytkowników systemu Windows: Znalazłem dobry sposób na uruchomienie asynchronicznego skryptu PHP (w rzeczywistości działa z prawie wszystkim).

Opiera się na poleceniach popen () i pclose (). Działa dobrze zarówno w systemie Windows, jak i Unix.

function execInBackground($cmd) {
    if (substr(php_uname(), 0, 7) == "Windows"){
        pclose(popen("start /B ". $cmd, "r")); 
    }
    else {
        exec($cmd . " > /dev/null &");  
    }
} 

Oryginalny kod z: http://php.net/manual/en/function.exec.php#86329

LucaM
źródło
to tylko wykonuje plik, nie działa, jeśli używasz php / python / node itp.
David Valentino
@davidvalentino Prawidłowo i w porządku! Jeśli chcesz wykonać skrypt PHP / Pyhton / NodeJS, musisz wywołać plik wykonywalny i przekazać mu swój skrypt. Np .: nie wkładasz terminala, myscript.jsale piszesz node myscript.js. To znaczy: węzeł jest plikiem wykonywalnym, myscript.js jest skryptem do wykonania. Istnieje ogromna różnica między plikiem wykonywalnym a skryptem.
LucaM
tak, nie ma problemu w takim przypadku, w innych przypadkach przykładowo, jak potrzeba uruchomienia rzemieślnika Laravela, php artisanpo prostu wstaw tutaj komentarz, więc nie trzeba śledzić, dlaczego nie będzie działać z poleceniami
David Valentino
22

W systemie Linux możesz wykonać następujące czynności:

$cmd = 'nohup nice -n 10 php -f php/file.php > log/file.log & printf "%u" $!';
$pid = shell_exec($cmd);

Spowoduje to wykonanie polecenia w wierszu polecenia, a następnie po prostu zwróci PID, który możesz sprawdzić dla> 0, aby upewnić się, że zadziałał.

To pytanie jest podobne: czy PHP ma wątki?

Darryl Hein
źródło
Ta odpowiedź byłaby łatwiejsza do odczytania, gdybyś uwzględnił tylko podstawowe elementy (eliminując action=generate var1_id=23 var2_id=35 gen_id=535segment). Ponadto, ponieważ OP poprosił o uruchomienie skryptu powłoki, nie potrzebujesz części specyficznych dla PHP. Ostateczny kod to:$cmd = 'nohup nice -n 10 /path/to/script.sh > /path/to/log/file.log & printf "%u" $!';
rinogo
Ponadto, jako notatka od osoby, która „była tam wcześniej”, każdy, kto to czyta, może rozważyć użycie nie tylko, niceale także ionice.
rinogo,
1
Co znaczy „% u” $! robić dokładnie?
Gałązki
@Twigs &uruchamia poprzedni kod w tle, a następnie printfsłuży do sformatowanego wyjścia $!zmiennej zawierającej PID
NoChecksum
1
Dziękuję bardzo, próbowałem wszelkiego rodzaju rozwiązań, które znalazłem, aby wyświetlić dane wyjściowe do dziennika za pomocą asynchronicznego wywołania powłoki PHP, a twoje jest jedynym, które spełnia wszystkie kryteria.
Josh Powlison
6

W systemie Linux można rozpocząć proces w nowym niezależnym wątku, dodając znak ampersand na końcu polecenia

mycommand -someparam somevalue &

W systemie Windows możesz użyć polecenia „start” DOS

start mycommand -someparam somevalue
Lew
źródło
2
W systemie Linux rodzic może nadal blokować, dopóki dziecko nie zakończy działania, jeśli próbuje odczytać z otwartego uchwytu pliku przechowywanego przez podproces (tj. Stdout), więc nie jest to kompletne rozwiązanie.
Charles Duffy
1
Przetestowane startpolecenie w systemie Windows, nie działa asynchronicznie ... Czy możesz podać źródło, z którego otrzymałeś te informacje?
Alph.Dev
@ Alph.Dev, proszę spojrzeć na moją odpowiedź, jeśli używasz systemu Windows: stackoverflow.com/a/40243588/1412157
LucaM
@ moja nazwa Twoja odpowiedź pokazuje dokładnie, dlaczego polecenie start NIE działało. Jest to spowodowane /Bparametrem. Wyjaśniłem to tutaj: stackoverflow.com/a/34612967/1709903
Alph.Dev,
6

właściwy sposób (!) to zrobić

  1. widelec()
  2. setsid ()
  3. execve ()

fork forks, setsid mówi bieżącemu procesowi, aby stał się nadrzędny (bez elementu nadrzędnego), wykonaj powiedz procesowi wywołującemu, aby został zastąpiony przez wywoływany aby rodzic mógł zrezygnować bez wpływu na dziecko.

 $pid=pcntl_fork();
 if($pid==0)
 {
   posix_setsid();
   pcntl_exec($cmd,$args,$_ENV);
   // child becomes the standalone detached process
 }

 // parent's stuff
 exit();

źródło
6
Problem z pcntl_fork () polega na tym, że nie powinieneś go używać podczas pracy pod serwerem WWW, jak robi to OP (poza tym OP już to próbował).
Guss
6

Użyłem tego ...

/** 
 * Asynchronously execute/include a PHP file. Does not record the output of the file anywhere.  
 * Relies on the PHP_PATH config constant.
 *
 * @param string $filename  file to execute
 * @param string $options   (optional) arguments to pass to file via the command line
 */ 
function asyncInclude($filename, $options = '') {
    exec(PHP_PATH . " -f {$filename} {$options} >> /dev/null &");
}

(gdzie PHP_PATHconst jest zdefiniowany jako podobny define('PHP_PATH', '/opt/bin/php5')lub podobny)

Przekazuje argumenty za pomocą wiersza polecenia. Aby przeczytać je w PHP, zobacz argv .

philfreo
źródło
4

Jedyny sposób, który naprawdę dla mnie zadziałał, to:

shell_exec('./myscript.php | at now & disown')
Gordon Forsythe
źródło
3
„disown” jest wbudowanym Bash i nie działa w ten sposób z shell_exec (). Próbowałem shell_exec("/usr/local/sbin/command.sh 2>&1 >/dev/null | at now & disown")i wszystko, co dostaję, to:sh: 1: disown: not found
Thomas Daugaard
4

Uważam również, że Symfony Process Component jest do tego przydatny.

use Symfony\Component\Process\Process;

$process = new Process('ls -lsa');
// ... run process in background
$process->start();

// ... do other things

// ... if you need to wait
$process->wait();

// ... do things after the process has finished

Zobacz, jak to działa w repozytorium GitHub .

Anton Pelykh
źródło
2
Ostrzeżenie: jeśli nie
zaczekasz
Idealne narzędzie oparte na proc_*funkcjach wewnętrznych.
MAChitgarha
2

Możesz również uruchomić skrypt PHP jako demon lub cronjob :#!/usr/bin/php -q

Ronald Conco
źródło
1

Użyj nazwanego fifo.

#!/bin/sh
mkfifo trigger
while true; do
    read < trigger
    long_running_task
done

Następnie, gdy chcesz rozpocząć długo działające zadanie, po prostu napisz nowy wiersz (nieblokujący do pliku wyzwalacza).

Tak długo, jak twój wkład jest mniejszy niż PIPE_BUFi jest to pojedyncza write()operacja, możesz pisać argumenty do fifo i wyświetlać je jak $REPLYw skrypcie.

geocar
źródło
1

bez kolejki użycia możesz użyć proc_open()następującego:

    $descriptorspec = array(
        0 => array("pipe", "r"),
        1 => array("pipe", "w"),
        2 => array("pipe", "w")    //here curaengine log all the info into stderror
    );
    $command = 'ping stackoverflow.com';
    $process = proc_open($command, $descriptorspec, $pipes);
Kris Roofe
źródło