¿Cómo se puede usar multi threading en aplicaciones PHP?

414

¿Existe una forma realista de implementar un modelo de subprocesos múltiples en PHP, ya sea de verdad o simplemente simulándolo? Hace algún tiempo, se sugirió que podría forzar al sistema operativo a cargar otra instancia del ejecutable de PHP y manejar otros procesos simultáneos.

El problema con esto es que cuando el código PHP termina de ejecutar la instancia de PHP permanece en la memoria porque no hay forma de eliminarlo desde PHP. Entonces, si está simulando varios hilos, puede imaginar lo que sucederá. Por lo tanto, todavía estoy buscando una forma en que se pueda realizar o simular de manera efectiva múltiples subprocesos desde PHP. ¿Algunas ideas?

Steve Obbayi
fuente
1
Vea mi pregunta y respuestas aquí: stackoverflow.com/questions/2101640/…
powtac
1
... y el mío aquí: stackoverflow.com/questions/209774/does-php-have-threading/…
Francois Bourgeois
cómo usar la extensión pthreads: phplobby.com/php-multi-thread-on-windows-pthreads-configuration
Emrah Mehmedov
Quizás de interés: pthreads.org
GibboK
Ahora en 2020, parece "paralelo" php.net/manual/en/intro.parallel.php es lo que queremos en lugar de "pthreads": stackoverflow.com/a/56451969/470749
Ryan

Respuestas:

431

Multi-threading es posible en php

Sí, puedes hacer subprocesos múltiples en PHP con pthreads

De la documentación de PHP :

pthreads es una API orientada a objetos que proporciona todas las herramientas necesarias para múltiples subprocesos en PHP. Las aplicaciones PHP pueden crear, leer, escribir, ejecutar y sincronizar con subprocesos, trabajadores y objetos subprocesados.

Advertencia : la extensión pthreads no se puede usar en un entorno de servidor web. Por lo tanto, el subprocesamiento en PHP debe quedar solo para las aplicaciones basadas en CLI.

Prueba simple

#!/usr/bin/php
<?php
class AsyncOperation extends Thread {

    public function __construct($arg) {
        $this->arg = $arg;
    }

    public function run() {
        if ($this->arg) {
            $sleep = mt_rand(1, 10);
            printf('%s: %s  -start -sleeps %d' . "\n", date("g:i:sa"), $this->arg, $sleep);
            sleep($sleep);
            printf('%s: %s  -finish' . "\n", date("g:i:sa"), $this->arg);
        }
    }
}

// Create a array
$stack = array();

//Initiate Multiple Thread
foreach ( range("A", "D") as $i ) {
    $stack[] = new AsyncOperation($i);
}

// Start The Threads
foreach ( $stack as $t ) {
    $t->start();
}

?>

Primer intento

12:00:06pm:     A  -start -sleeps 5
12:00:06pm:     B  -start -sleeps 3
12:00:06pm:     C  -start -sleeps 10
12:00:06pm:     D  -start -sleeps 2
12:00:08pm:     D  -finish
12:00:09pm:     B  -finish
12:00:11pm:     A  -finish
12:00:16pm:     C  -finish

Segunda carrera

12:01:36pm:     A  -start -sleeps 6
12:01:36pm:     B  -start -sleeps 1
12:01:36pm:     C  -start -sleeps 2
12:01:36pm:     D  -start -sleeps 1
12:01:37pm:     B  -finish
12:01:37pm:     D  -finish
12:01:38pm:     C  -finish
12:01:42pm:     A  -finish

Ejemplo del mundo real

error_reporting(E_ALL);
class AsyncWebRequest extends Thread {
    public $url;
    public $data;

    public function __construct($url) {
        $this->url = $url;
    }

    public function run() {
        if (($url = $this->url)) {
            /*
             * If a large amount of data is being requested, you might want to
             * fsockopen and read using usleep in between reads
             */
            $this->data = file_get_contents($url);
        } else
            printf("Thread #%lu was not provided a URL\n", $this->getThreadId());
    }
}

$t = microtime(true);
$g = new AsyncWebRequest(sprintf("http://www.google.com/?q=%s", rand() * 10));
/* starting synchronization */
if ($g->start()) {
    printf("Request took %f seconds to start ", microtime(true) - $t);
    while ( $g->isRunning() ) {
        echo ".";
        usleep(100);
    }
    if ($g->join()) {
        printf(" and %f seconds to finish receiving %d bytes\n", microtime(true) - $t, strlen($g->data));
    } else
        printf(" and %f seconds to finish, request failed\n", microtime(true) - $t);
}
Baba
fuente
3
@Baba, no puedo configurar e instalar pthreads en el servidor Xampp. ¿Me puede ayudar con eso?
Irfan
44
Descargue el binario de Windows aquí windows.php.net/downloads/pecl/releases/pthreads/0.0.45
Baba
17
Eso es bueno, no he tocado PHP durante años y ahora tiene capacidades de subprocesamiento múltiple.
Crucero
1
Agradable y simple! Solo para su información, estoy implementando una aplicación en el servidor Azure Cloud Win y si solo se selecciona la configuración básica de 1 núcleo, el subproceso múltiple no estará disponible a menos que se agreguen más núcleos.
Milán
3
Tenga cuidado: Joe Watkins, el autor de la extensión pthreads suspendió el desarrollo a favor de la nueva extensión paralela: github.com/krakjoe/pthreads/issues/929
Anton Belonovich
42

¿Por qué no usas popen ?

for ($i=0; $i<10; $i++) {
    // open ten processes
    for ($j=0; $j<10; $j++) {
        $pipe[$j] = popen('script2.php', 'w');
    }

    // wait for them to finish
    for ($j=0; $j<10; ++$j) {
        pclose($pipe[$j]);
    }
}
masterb
fuente
44
Estoy usando la solución anterior, y funciona bien, creo que fue la forma más fácil de hacer un proceso paralelo usando php.
Padrino
77
como @ e-info128 dijo, esta implementación bifurca el proceso, lo que significa que se está ejecutando en un proceso diferente y no comparte los recursos del proceso. Dicho esto, si el trabajo en cuestión no necesita compartir recursos, entonces seguirá funcionando y se ejecutará en paralelo.
Raffi
1
¿Cómo pasarías las variables a popen sin usar variables de sesión?
atwellpub
@atwellpub De ninguna manera, estos son procesos separados que no comparten recursos. Incluso las sesiones serán un mecanismo de IPC incómodo
Cholthi Paul Ttiopic
1
Para pasarles datos, también puede usar argumentos y el servidor Redis.
Amir Fo
21

El enhebrado no está disponible en stock PHP, pero la programación concurrente es posible mediante el uso de solicitudes HTTP como llamadas asincrónicas.

Con la configuración de tiempo de espera del rizo establecida en 1 y utilizando el mismo session_id para los procesos que desea asociar entre sí, puede comunicarse con las variables de sesión como en mi ejemplo a continuación. Con este método, incluso puede cerrar su navegador y el proceso concurrente todavía existe en el servidor.

No olvide verificar la ID de sesión correcta de esta manera:

http: //localhost/test/verifysession.php? sessionid = [la identificación correcta]

startprocess.php

$request = "http://localhost/test/process1.php?sessionid=".$_REQUEST["PHPSESSID"];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $request);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 1);
curl_exec($ch);
curl_close($ch);
echo $_REQUEST["PHPSESSID"];

process1.php

set_time_limit(0);

if ($_REQUEST["sessionid"])
   session_id($_REQUEST["sessionid"]);

function checkclose()
{
   global $_SESSION;
   if ($_SESSION["closesession"])
   {
       unset($_SESSION["closesession"]);
       die();
   }
}

while(!$close)
{
   session_start();
   $_SESSION["test"] = rand();
   checkclose();
   session_write_close();
   sleep(5);
}

verifysession.php

if ($_REQUEST["sessionid"])
    session_id($_REQUEST["sessionid"]);

session_start();
var_dump($_SESSION);

closeprocess.php

if ($_REQUEST["sessionid"])
    session_id($_REQUEST["sessionid"]);

session_start();
$_SESSION["closesession"] = true;
var_dump($_SESSION);
Ricardo
fuente
44
La última vez que revisé (hace unos años) php no permitió acceder al almacenamiento de sesión basado en archivos mediante dos procesos simultáneamente. Bloquea el archivo y el segundo proceso debe permanecer allí esperando a que se detenga el primer script. Estoy hablando del entorno del servidor web, no de la CLI.
Alexei Tenitski
55
set_time_limit(0);¡Ay! Nunca, nunca hagas esto.
Kafoso
@Kafoso Kafoso ¿por qué no? Bueno, estoy de acuerdo con PHP como procesador de scripts web, pero ¿por qué no en CLI? Si algo sale mal, CLI puede ser eliminado con Ctrl + C ...
sijanec
14

Si bien no puede enhebrar, tiene cierto grado de control de proceso en php. Los dos conjuntos de funciones que son útiles aquí son:

Funciones de control de procesos http://www.php.net/manual/en/ref.pcntl.php

Funciones POSIX http://www.php.net/manual/en/ref.posix.php

Puede bifurcar su proceso con pcntl_fork, devolviendo el PID del niño. Luego puede usar posix_kill para desechar ese PID.

Dicho esto, si matas un proceso padre, se debe enviar una señal al proceso hijo diciéndole que muera. Si php no reconoce esto, puede registrar una función para administrarlo y hacer una salida limpia usando pcntl_signal.

JD Fitz.Gerald
fuente
1
Esa respuesta ahora está muy desactualizada (lo cual es muy justo sabiendo que tiene 11 años). Mira pthreads a continuación.
Maciej Paprocki
@MaciejPaprocki pThread ahora se descontinuó de php 7.4 en lugar de usar paralelo
Airy
10

Sé que esta es una pregunta antigua, pero para las personas que buscan, hay una extensión PECL escrita en C que ahora brinda la capacidad de subprocesamiento múltiple de PHP, se encuentra aquí https://github.com/krakjoe/pthreads

JasonDavis
fuente
pThread ahora se descontinuó de php 7.4 en lugar de usar paralelo
Airy
5

Puede usar exec () para ejecutar un script de línea de comando (como php de línea de comando), y si canaliza la salida a un archivo, entonces su script no esperará a que termine el comando.

No puedo recordar la sintaxis de la CLI de php, pero querrías algo como:

exec("/path/to/php -f '/path/to/file.php' | '/path/to/output.txt'");

Creo que algunos servidores de alojamiento compartido tienen exec () deshabilitado de forma predeterminada por razones de seguridad, pero podría valer la pena intentarlo.

Adam Hopkinson
fuente
4

Podrías simular el enhebrado. PHP puede ejecutar procesos en segundo plano a través de popen (o proc_open). Esos procesos pueden comunicarse con stdin y stdout. Por supuesto, esos procesos pueden ser un programa php. Eso es probablemente lo más cerca que puedas llegar.

Pete
fuente
3

Dependiendo de lo que intentes hacer, también puedes usar curl_multi para lograrlo.

Sheldmandu
fuente
3

Sé que esto es muy antiguo, pero puedes mirar http://phpthreadlib.sourceforge.net/

Es compatible con la comunicación bidireccional entre subprocesos y también tiene protecciones integradas para eliminar los subprocesos de los niños (prevención de huérfanos).

No firmado
fuente
3

Puede tener la opción de:

  1. multi_curl
  2. Uno puede usar el comando del sistema para el mismo
  3. El escenario ideal es crear una función de subprocesamiento en lenguaje C y compilar / configurar en PHP. Ahora esa función será la función de PHP.
Manoj Donga
fuente
3

La clase Thread está disponible desde PECL pthreads ≥ 2.0.0.

Dhananjay Kashyap
fuente
1
¿Puedo ejecutar hilo por HTTP? simple: yourname.com/thread.php?
Mi nombre
pThread ahora se descontinuó de php 7.4 en lugar de usar paralelo
Airy
3

¿Qué tal pcntl_fork?

consulte nuestra página del manual para ver ejemplos: PHP pcntl_fork

<?php

    $pid = pcntl_fork();
    if ($pid == -1) {
        die('could not fork');
    } else if ($pid) {
        // we are the parent
        pcntl_wait($status); //Protect against Zombie children
    } else {
        // we are the child
    }

?>
Jarrod
fuente
2

pcntl_forkno funcionará en un entorno de servidor web si tiene activado el modo seguro . En este caso, solo funcionará en la versión CLI de PHP.

Stilero
fuente
1

Si está utilizando un servidor Linux, puede usar

exec("nohup $php_path path/script.php > /dev/null 2>/dev/null &")

Si necesitas pasar algunos args

exec("nohup $php_path path/script.php $args > /dev/null 2>/dev/null &")

En script.php

$args = $argv[1];

O use Symfony https://symfony.com/doc/current/components/process.html

$process = Process::fromShellCommandline("php ".base_path('script.php'));
$process->setTimeout(0);     
$process->disableOutput();     
$process->start();
Юрий Ярвинен
fuente
-1

Al momento de escribir mi comentario actual, no sé sobre los hilos de PHP. Vine a buscar la respuesta aquí mismo, pero una solución es que el programa PHP que recibe la solicitud del servidor web delega toda la formulación de la respuesta a una aplicación de consola que almacena su salida, la respuesta a la solicitud, en un archivo binario y el programa PHP que lanzó la aplicación de consola devuelve ese archivo binario byte por byte como respuesta a la solicitud recibida. La aplicación de consola se puede escribir en cualquier lenguaje de programación que se ejecute en el servidor, incluidos los que tienen soporte de subprocesamiento adecuado, incluidos los programas C ++ que usan OpenMP.

Un truco poco confiable y sucio es usar PHP para ejecutar una aplicación de consola, "uname",

uname -a

e imprima la salida de ese comando de consola en la salida HTML para averiguar la versión exacta del software del servidor. Luego instale exactamente la misma versión del software en una instancia de VirtualBox, compile / ensamble los binarios completamente autónomos, preferiblemente estáticos, que desee y luego cárguelos al servidor. A partir de ese momento, la aplicación PHP puede usar esos binarios en el rol de la aplicación de consola que tiene un subproceso múltiple adecuado. Es una solución sucia, poco confiable, para una situación, cuando el administrador del servidor no ha instalado todas las implementaciones de lenguaje de programación necesarias en el servidor. Lo que hay que tener en cuenta es que en cada solicitud que la aplicación PHP recibe, la (s) aplicación (es) de la consola finalizan / exit / get_killed.

En cuanto a lo que piensan los administradores del servicio de alojamiento de dichos patrones de uso del servidor, supongo que todo se reduce a la cultura. En el norte de Europa, el proveedor de servicios TIENE QUE ENTREGAR LO QUE SE ANUNCIÓ y si se permitía la ejecución de comandos de consola y se permitía la carga de archivos que no sean malware y el proveedor de servicios tiene derecho a eliminar cualquier proceso del servidor después de unos minutos o incluso después de 30 segundos , los administradores del servicio de alojamiento carecen de argumentos para presentar una queja adecuada. En los Estados Unidos y Europa occidental, la situación / cultura es muy diferente y creo que hay una gran posibilidad de que en los Estados Unidos y / o Europa occidental el proveedor de servicios de alojamiento se niegue a servir a los clientes que utilizan el truco descrito anteriormente. Es solo mi suposición, dada mi experiencia personal con nosotros servicios de alojamiento y dado lo que he escuchado de otros sobre los servicios de alojamiento de Europa occidental. Al momento de escribir mi comentario actual (2018_09_01) no sé nada sobre las normas culturales de los proveedores de servicios de alojamiento del sur de Europa, los administradores de red del sur de Europa.

Martin Vahi
fuente
-3

Puede ser que me haya perdido algo, pero exec no funcionó tan asíncrono para mí en el entorno de Windows que utilicé en Windows y funcionó de maravilla;)

$script_exec = "c:/php/php.exe c:/path/my_ascyn_script.php";

pclose(popen("start /B ". $script_exec, "r"));
Mubashar
fuente
1
Para hackear múltiples subprocesos en PHP, debe abrir varias declaraciones popen () (PHP no esperará a que se complete). Luego, después de abrir media docena más o menos, llama a pclose () en esos (PHP esperará a que se complete el pclose ()). En su código, cierra cada hilo inmediatamente después de abrirlo para que su código no se comporte como multiproceso.
Kmeixner
-5

Multithreading significa realizar múltiples tareas o procesos simultáneamente, podemos lograr esto en php usando el siguiente código, aunque no hay una forma directa de lograr multihilo en php, pero podemos lograr casi los mismos resultados de la siguiente manera.

chdir(dirname(__FILE__));  //if you want to run this file as cron job
 for ($i = 0; $i < 2; $i += 1){
 exec("php test_1.php $i > test.txt &");
 //this will execute test_1.php and will leave this process executing in the background and will go         

 //to next iteration of the loop immediately without waiting the completion of the script in the   

 //test_1.php , $i  is passed as argument .

}

Test_1.php

$conn=mysql_connect($host,$user,$pass);
$db=mysql_select_db($db);
$i = $argv[1];  //this is the argument passed from index.php file
for($j = 0;$j<5000; $j ++)
{
mysql_query("insert  into  test   set

                id='$i',

                comment='test',

                datetime=NOW() ");

}

Esto ejecutará test_1.php dos veces simultáneamente y ambos procesos se ejecutarán en segundo plano simultáneamente, por lo que de esta manera puede lograr múltiples subprocesos en php.

Este tipo hizo un trabajo realmente bueno Multithreading en php

Pir Abdul
fuente
Además, esto no tiene nada que hacer con MultiThreading. Esto es procesamiento paralelo. Cosas totalmente diferentes.
Digital Human
En mi opinión como una solución alternativa, un truco de emergencia, la idea detrás de la solución ofrecida es muy apropiada, pero supongo que diferentes personas pueden tener guerras de fuego sobre lo que constituye "verdadero subprocesamiento múltiple", porque hay una distinción entre concurrencia y hardware procesamiento paralelo, como se describe en: youtube.com/watch?v=cN_DpYBzKso
Martin Vahi