php bir arka plan işlemi yürütmek


259

Bir kullanıcı eylemi üzerine bir dizin kopya yürütmek gerekiyor, ancak dizinleri oldukça büyük, bu yüzden kullanıcı kopyasını tamamlamak için gereken sürenin farkında olmadan böyle bir eylem gerçekleştirmek istiyorum.

Herhangi bir öneriniz çok takdir edilecektir.


3
Bu stackoverflow.com/questions/5367261/… bunu pencerelerin altında nasıl yapacağınızı açıklar
shealtiel

Yanıtlar:


365

Bunun bir Linux makinesinde çalıştığını varsayarsak, her zaman şu şekilde ele aldım:

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

Bu, komutu başlatır, komut $cmdçıkışını yeniden yönlendirir $outputfileve işlem kimliğini yazar $pidfile.

Bu, işlemin ne yaptığını ve hala çalışıp çalışmadığını kolayca izlemenizi sağlar.

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
Sudo kurulumu parola sorulmadan mı çalıştırılıyor? Kullanıcı girişi gerektiren komutlar çalışmaz.
Mark Biek

17
Bu stackoverflow.com/questions/5367261/… pencerelerin altında nasıl yapılacağını açıklar
shealtiel

13
Bunu kullandım, ancak PID'yi bir dosyaya yazmak zorunda kalmadan biraz güncelledim. Bu biçimi kullanıyorum: <code> exec (sprintf ("$ s> $ s 2> & 1 & echo $ 1", $ cmd, $ outputfile), $ pidArr); </code> Ortaya çıkan işlem PID'si $ pidArr [0]
Kaiesh

3
@Kaiesh: "echo $!" Olmalı
brunobg

8
Kaiesh anot yazım hatası yaptı, '%' yerine '$'. Düzeltilmiş sürüm: exec (sprintf ("% s>% s 2> & 1 & echo $!", $ Cmd, $ outputfile), $ pidArr)
Yuri Gor

23

İşlemi hangi dilde (php / bash / perl / vb.) Kullanışlı bir sunucu tarafı komut dosyası olarak yazın ve sonra php komut dosyanızdaki işlem denetim işlevlerinden çağırın.

Fonksiyon büyük olasılıkla çıkış akışı olarak standart io kullanılıp kullanılmadığını algılar ve eğer öyleyse, bu dönüş değerini ayarlayacaktır.

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

Komut olarak "uyku 25s" kullanarak komut satırından hızlı bir şekilde test ve bir cazibe gibi çalıştı.

( Yanıt burada bulundu )


3
Centos üzerinde çalışabilmemin tek yolu bu. Teşekkür ederim!
Deac Karns

Teşekkürler ! PHP ve BASH ile uyumsuzluk var. Daha yeni bir sistem çalıştırırsanız, system () veya exec () kullanarak arka planda başlatılamaz. Dosya tanımlayıcıları, yeniden yönlendirseniz bile takılıyor gibi görünüyor. Metodunuz işe yaradı. başka bir alternatif PHP için eski bir bash ikili kullanmaktır, ama bu fındık
John

22

Bunu komutunuza eklemeyi deneyebilirsiniz

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

Örneğin.

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

Demek istediğim> / dev / null 2> / dev / null &
wlf

Benim için çalışıyor ve makefile'ı sunucu sitesinde bir şey yapacağım, teşekkür ederim! (ör. yeniden başlat, yeniden başlat)
Chu-Siang Lai

Bu, kabul edilen cevaptan çok daha basit! (Sürecin hala çalışıp çalışmadığını kontrol etmek gibi daha karmaşık bir şeye ihtiyacınız olmadığı sürece.)
Sean the Bean

18

Bu işlevi Windows'ta test etmek için çok basit bir örnek eklemek istiyorum:

Aşağıdaki iki dosyayı oluşturun ve bir web dizinine kaydedin:

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);
?>

IUSR'a yukarıdaki dosyaları oluşturduğunuz dizine yazma izni verin

IUSR'ye C: \ Windows \ System32 \ cmd.exe dosyasını OKUYUN ve YÜRÜTÜN

Bir web tarayıcısından foreground.php'ye basın

Tarayıcıya, çıkış dizisindeki geçerli zaman damgaları ve yerel kaynak # ile aşağıdakiler yapılmalıdır:

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

Testoutput.php dosyasını yukarıdaki dosyalar ile aynı dizinde görmelisiniz ve boş olmalıdır

Testprocesses.php dosyasını yukarıdaki dosyalar ile aynı dizinde görmelisiniz ve geçerli zaman damgalarıyla birlikte aşağıdaki metni içermelidir:

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

10

PHP sayfasının tamamlanmasını beklemeden arka planda bir şey yapmanız gerekiyorsa, wget komutuyla "çağrılmış" başka bir (arka plan) PHP komut dosyası kullanabilirsiniz. Bu arka plan PHP betiği, elbette, sisteminizdeki diğer PHP betiği gibi ayrıcalıklarla yürütülecektir.

İşte gnuwin32 paketlerinden wget kullanan bir Windows örneği.

Örnek olarak arka plan kodu (file test-proc-bg.php) ...

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

Ön plan senaryosu, davet eden ...

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

Düzgün çalışması için popen / pclose komutunu kullanmalısınız.

Wget seçenekleri:

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

7

İşte PHP'de bir arka plan işlemi başlatmak için bir işlev. Sonunda, farklı yaklaşımları ve parametreleri okuduktan ve test ettikten sonra, aslında Windows üzerinde de çalışan bir tane oluşturdu.

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;
  }
}

Not 1: Pencerelerde, /Bbaşka bir yerde önerildiği gibi parametre kullanmayın . İşlemi startkomutun kendisiyle aynı konsol penceresini çalıştırmaya zorlar ve işlemin eşzamanlı olarak işlenmesini sağlar. İşlemi ayrı bir iş parçacığında (eşzamansız olarak) çalıştırmak için kullanmayın /B.

Not 2: start ""Komutun tırnak içine alınmış bir yol olması durumunda, boş çift tırnak işareti gereklidir. startkomutu, ilk belirtilen parametreyi pencere başlığı olarak yorumlar.


6

Kullanımı biraz daha hızlı ve daha kolay bir sürüm buldum

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

ve çalışıyor.


@ Alpha.Dev linux üzerinde test ettim ancak başka bir sistem üzerinde çalışıp çalışmayacağını bilmiyorum.
Morfidon

4

Ayrı bir işlemi kesip, kopyanızı arka planda çalıştırabilir misiniz? Herhangi bir PHP yaptığımdan beri bir süre oldu, ancak pcntl-fork işlevi umut verici görünüyor.


Bu soruya bir cevap sağlamaz. Bir yazardan eleştiri veya açıklama istemek için gönderilerinin altına bir yorum bırakın.
Rizier123

13
Teşekkürler - Yorumlar '08'de yoktu, ama bunu aklımda tutacağım :)
Matt Sheppard

4

Programınızı arka planda çalıştırmak için bu işlevi kullanın. Çapraz platform ve tamamen özelleştirilebilir.

<?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;
}

Komut başlatıldıktan sonra, varsayılan olarak bu işlevin çalışma işleminin stdin ve stdout'unu kapattığını unutmayın. İşlem çıktısını $ redirectStdout ve $ redirectStderr argümanları aracılığıyla bazı dosyalara yönlendirebilirsiniz.

Windows kullanıcıları için not:
stdout / stderr öğesini nulaşağıdaki şekilde yeniden yönlendiremezsiniz :

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

Ancak bunu yapabilirsiniz:

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

* Nix kullanıcıları için notlar:

1) Gerçek PID almak istiyorsanız exec shell komutunu kullanın:

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

2) Programınızın girişine bazı veriler aktarmak istiyorsanız $ stdin argümanını kullanın:

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

3

Resque gibi bir kuyruk sistemini deneyebilirsiniz . Daha sonra, bilgileri işleyen ve "işleme" görüntüsü ile oldukça hızlı bir şekilde geri dönen bir iş oluşturabilirsiniz. Bu yaklaşımla, ne zaman bittiğini bilemezsiniz.

Bu çözüm, ön makinelerinizin ağır kaldırma işlemini yapmasını istemediğiniz daha büyük ölçekli uygulamalar için tasarlanmıştır, böylece kullanıcı isteklerini işleyebilirler. Bu nedenle, dosyalar ve klasörler gibi fiziksel verilerle çalışabilir veya çalışmayabilir, ancak daha karmaşık mantık veya diğer eşzamansız görevleri (yani yeni kayıt postaları) işlemek için olması güzel ve çok ölçeklenebilir.


2

Hem Windows hem de Linux için çalışan bir çözüm. Daha fazla bilgiyi github sayfamda bulabilirsiniz .

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

Bir arka plan işlemi başlatmak yerine, bir tetikleyici dosyası oluşturmak ve tetikleyici dosyalarını arayan ve bunlara etki eden bir komut dosyasını periyodik olarak cron veya autosys gibi bir zamanlayıcıya sahip olmaya ne dersiniz? Tetikleyiciler talimatlar veya hatta ham komutlar içerebilir (daha iyisi, sadece bir kabuk betiği yap).



1

Çok kullanıyorum fast_cgi_finish_request()

Kapatma ve register_shutdown_function () ile birlikte

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

Ardından, kapanmadan önce yürütülecek bu kapatmayı kaydedin.

register_shutdown_function($backgroundJob);

Son olarak, yanıt istemciye gönderildiğinde, istemciyle bağlantıyı kapatabilir ve PHP işlemiyle çalışmaya devam edebilirsiniz:

fast_cgi_finish_request();

Kapatma fast_cgi_finish_request sonrasında yürütülecektir.

$ Mesajı hiçbir zaman görünmez. Ve istediğiniz kadar kapanış kaydedebilirsiniz, ancak komut dosyası yürütme süresine dikkat edin. Bu sadece PHP bir Fast CGI modülü olarak çalışıyorsa işe yarar (doğru muydu ?!)


0

PHP komut dosyaları diğer masaüstü uygulama geliştirme dili gibi değildir. Masaüstü uygulama dillerinde arka plan iş parçacıklarını arka plan işlemini çalıştıracak şekilde ayarlayabiliriz, ancak PHP'de kullanıcı bir sayfa istediğinde bir işlem gerçekleşir. Ancak php betiğinin çalıştığı sunucunun cron işi işlevini kullanarak bir arka plan işi ayarlamak mümkündür.


0

Windows kullananlarımız için şuna bakın:

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

Ben de komut dosyası yürütülmeye devam ederken Windows arka planda çalıştırmak için bir program alma ile güreşti. Bu yöntem, diğer çözümlerin aksine, küçültülmüş, büyütülmüş veya hiç penceresiz herhangi bir program başlatmanıza izin verir. llbra @ phpbrasil'in çözümü işe yarıyor ancak görevin gerçekten gizli çalışmasını istediğinizde bazen masaüstünde istenmeyen bir pencere oluşuyor.

Notepad.exe'yi arka planda simge durumuna küçültülmüş olarak başlatın:

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

arka planda görünmeyen bir kabuk komutu başlatın:

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

maksimize edilmiş MSPaint'i başlatın ve betiğe devam etmeden önce kapatmanızı bekleyin:

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

Run () yöntemi hakkında daha fazla bilgi için şu adrese gidin: http://msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp

Düzenlenen URL:

Yukarıdaki bağlantı artık mevcut olmadığından, bunun yerine https://technet.microsoft.com/en-us/library/ee156605.aspx adresine gidin .


1
Bu kodu çalıştırmak için ne yapılması gerekiyor? AnladımFatal error: Class 'COM' not found
Alph.Dev

Ps MSDN Bağlantı koptu
Alph.Dev

@ Alph.Dev: Msdn dokümanı geçici olarak kaldırılmış gibi görünüyor. WScript.Shell'in nasıl kullanılacağına ilişkin daha fazla örnek ve açıklama içermektedir.
Jimmy Ilenloa

@ Alph.Dev: COM sınıfı bulunamadığı için, bu tamamen başka bir şey. Google'da kontrol edin. Bu php.net/manual/tr/com.installation.php adresini de kontrol edebilirsiniz.
Jimmy Ilenloa

@ Alph.Dev Run () işlevi hakkında güncellenmiş bilgi için technet.microsoft.com/tr-lib/library/ee156605.aspx adresini ziyaret edin
Jimmy Ilenloa

-12

100 yıllık bir yazı olduğunu biliyorum, ama yine de, birisi için yararlı olabileceğini düşündüm. Sayfada, arka planda çalışması gereken URL'yi gösteren bir yere görünmez bir görüntü koyabilirsiniz:

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


12
Gerçekten kötü bir çözüm. Kullanıcı sayfadan ayrılırsa işlem yarıda kesilir.
Sergey P. aka azure

3
"görünmez resim" ve bağlantısı sayfa kaynak kodunda gösterilir.
tony gil
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.