Виконання асинхронної оболонки в PHP


199

У мене є скрипт PHP, який повинен викликати скрипт оболонки, але зовсім не хвилює вихід. Сценарій оболонки робить ряд викликів SOAP і повільно завершується, тому я не хочу сповільнювати запит PHP, поки він чекає відповіді. Фактично, PHP-запит повинен мати можливість вийти, не припиняючи процес оболонки.

Я подивився в різні exec(), shell_exec(), pcntl_fork()функції і т.д., але жоден з них не здається, пропонують саме те , що я хочу. (Або, якщо вони є, мені незрозуміло, як.) Будь-які пропозиції?


Незалежно від того, яке рішення ви не вибрали, слід також розглянути питання про використання niceта ioniceзапобігання тому, щоб скрипт оболонки перевантажував вашу систему (наприклад /usr/bin/ionice -c3 /usr/bin/nice -n19)
rinogo

Відповіді:


218

Якщо це "не хвилює вихід", чи не може викликати виконавець сценарію з &фоном для процесу?

EDIT - включивши те, що @ AdamTheHut прокоментував цю публікацію, ви можете додати це до дзвінка на exec:

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

Це дозволить перенаправити і stdio(перше >), і stderr( 2>), /dev/nullі запустити у фоновому режимі.

Є й інші способи зробити те саме, але це найпростіший для читання.


Альтернатива вищевказаному подвійному переадресації:

" &> /dev/null &"

11
Це, здається, працює, але для цього потрібно трохи більше, ніж амперсанд. У мене це працює, додавши "> / dev / null 2> / dev / null &" у виклик exec (). Хоча мушу визнати, що я не зовсім впевнений, що це робить.
AdamTheHutt

2
Однозначно шлях, якщо ви хочете вогонь і забути за допомогою php та apache. У багатьох виробничих середовищах Apache та PHP буде відключено pcntl_fork ().
метод

9
Лише зауваження &> /dev/null &, що xdebug не створюватиме журнали, якщо ви користуєтесь цим. Перевірити stackoverflow.com/questions/4883171 / ...
kapeels

5
@MichaelJMulligan він закриває дескриптори файлів. Однак, незважаючи на підвищення ефективності, /dev/nullкращою практикою є використання заднім числом, оскільки запис у закриті FD викликає помилки, тоді як спроби читання чи запису /dev/nullпросто мовчки нічого не роблять.
Чарльз Даффі

3
Фонові сценарії будуть вбиті при перезапуску апачу ... просто знайте про це на дуже довгих робочих місцях або якщо вам не пощастило, і вам цікаво, чому ваша робота зникла ...
Жюльєн

53

Я використовував в для цього, оскільки це дійсно починає незалежний процес.

<?php
    `echo "the command"|at now`;
?>

4
в деяких ситуаціях це абсолютно найкраще рішення. це єдиний, хто працював для мене, щоб випустити "перезавантаження sudo" ("ехо" сон 3; перезавантаження судо "| зараз") з вебгуї та закінчити візуалізацію сторінки .. на openbsd
Kaii

1
якщо користувач, якому ви керуєте apache (як правило www-data), не має дозволів на використання, atі ви не можете його налаштувати, ви можете спробувати використовувати <?php exec('sudo sh -c "echo \"command\" | at now" ');Якщо commandмістять цитати, див. escapeshellarg, щоб врятувати від головного болю
Julien

1
Добре подумати про це, щоб отримати sudo для запуску sh - не найкраща ідея, оскільки це, по суті, дає sudo кореневий доступ до всього. Я повернувся до використання echo "sudo command" | at nowта коментування www-dataв/etc/at.deny
Жюльєн

@Julien, можливо, ви можете зробити скрипт оболонки за допомогою команди sudo і запустити її замість цього. до тих пір, поки ви не
передасте

26

Для всіх користувачів Windows: я знайшов хороший спосіб запустити асинхронний скрипт PHP (насправді він працює практично з усім).

Він заснований на командах popen () та pclose (). І добре працює як в Windows, так і в Unix.

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

Оригінальний код від: http://php.net/manual/en/function.exec.php#86329


це лише виконати файл, а не працювати, якщо використовується php / python / node тощо
david valentino

@davidvalentino Правильно, і це добре! Якщо ви хочете виконати сценарій PHP / Pyhton / NodeJS, вам потрібно насправді викликати виконуваний файл і передати йому свій сценарій. Наприклад: ви не ставите у свій термінал, myscript.jsа натомість напишите node myscript.js. Тобто: вузол - це виконуваний файл, myscript.js - це сценарій для виконання. Існує величезна різниця між виконуваним файлом та сценарієм.
ЛукаM

правильно, це не проблема в цьому випадку ,, в інших випадках, наприклад, наприклад, потрібно запустити laravel ремісника, php artisanпросто поставте коментар тут, так що не потрібно простежувати, чому він не працює з командами
David Valentino

22

У Linux можна зробити наступне:

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

Це виконає команду в командному рядку, а потім просто поверне PID, який ви можете перевірити на> 0, щоб переконатися, що він працював.

Це питання схоже: чи PHP має нарізку?


Цю відповідь було б легше прочитати, якби ви включили лише голові основи (виключаючи action=generate var1_id=23 var2_id=35 gen_id=535сегмент). Крім того, оскільки ОП запитав про запуск сценарію оболонки, вам не потрібні окремі для PHP частини. Остаточним кодом буде:$cmd = 'nohup nice -n 10 /path/to/script.sh > /path/to/log/file.log & printf "%u" $!';
rinogo

Крім того, як замітка того, хто "був там раніше", кожен, хто читає це, може розглянути можливість використання не просто, niceа й ionice.
rinogo

1
Що означає "% u" $! робити саме?
Гілочки

@Twigs &виконує попередній код у фоновому режимі, потім printfвикористовується для форматованого виводу $!змінної, яка містить PID
NoChecksum

1
Дякую вам велике, я спробував усілякі рішення, які знаходив для виведення в журнал із викликом оболонки async PHP, і ваш - єдиний, хто виконав усі критерії.
Джош Поллісон


6

В Linux ви можете запустити процес у новому незалежному потоці, додавши амперсанд в кінці команди

mycommand -someparam somevalue &

У Windows можна використовувати команду "start" DOS

start mycommand -someparam somevalue

2
В Linux батько все ще може блокувати, поки дитина не закінчиться, якщо він намагається прочитати з відкритого файлу, що знаходиться в підпроцесі (тобто stdout), тому це не є повним рішенням.
Чарльз Даффі

1
Випробувана startкоманда у Windows, вона не працює асинхронно ... Чи можете ви включити джерело, звідки ви отримали цю інформацію?
Alph.Dev

@ Alph.Dev ласка , подивися на мою відповідь , якщо ви використовуєте Windows: stackoverflow.com/a/40243588/1412157
LucaM

@mynameis Ваша відповідь точно показує, чому команда start НЕ працює. Його через /Bпараметр. Я пояснив це тут: stackoverflow.com/a/34612967/1709903
Alph.Dev

6

правильний спосіб (!) зробити це - це

  1. вилка ()
  2. setid ()
  3. execve ()

fork forks, setid повідомляє поточному процесу стати головним (без батьків), execve повідомити процес виклику, який повинен бути замінений на викликаний. так що батько може кинути, не впливаючи на дитину.

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

 // parent's stuff
 exit();

6
Проблема з pcntl_fork () полягає в тому, що ви не повинні використовувати його під час роботи під веб-сервером, як це робить ОП (до того ж, ОП вже пробували це).
Гасс

6

Я використав це ...

/** 
 * 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 &");
}

(де PHP_PATHconst визначено як define('PHP_PATH', '/opt/bin/php5')або подібне)

Він передається в аргументах через командний рядок. Щоб прочитати їх у PHP, див. Argv .


4

Єдиний спосіб, який я виявив, що справді працював для мене, це:

shell_exec('./myscript.php | at now & disown')

3
'disown' - це вбудований Bash і не працює з shell_exec () таким чином. Я спробував, shell_exec("/usr/local/sbin/command.sh 2>&1 >/dev/null | at now & disown")і все, що я отримую:sh: 1: disown: not found
Томас Даугаард

4

Для цього я також вважаю корисним компонент Symfony Process .

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

Подивіться, як це працює в його репо GitHub .


2
Попередження: якщо ви не дочекаєтесь, процес буде вбито, коли запит закінчиться
the_nuts

Ідеальний інструмент, заснований на proc_*внутрішніх функціях.
MAChitgarha


1

Використовуйте названу фіфо.

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

Тоді, коли ви хочете почати тривале завдання, просто напишіть новий рядок (нерозблокуючи файл тригера.

Поки ваш вхід менший, ніж PIPE_BUFце одна write()операція, ви можете записувати аргументи у fifo і показувати їх, як $REPLYу сценарії.


1

без черги використання ви можете використовувати proc_open()так:

    $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);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.