¿PHP tiene subprocesos?

Resuelto Thomas Owens asked hace 54 años • 13 respuestas

Encontré este paquete PECL llamado threads , pero aún no hay una versión. Y no aparece nada en el sitio web de PHP.

Thomas Owens avatar Jan 01 '70 08:01 Thomas Owens
Aceptado

Del manual de PHP para la extensión pthreads :

pthreads es una API orientada a objetos que permite subprocesos múltiples en PHP. Incluye todas las herramientas que necesita para crear aplicaciones multiproceso dirigidas a la Web o la Consola. Las aplicaciones PHP pueden crear, leer, escribir, ejecutar y sincronizar con Threads, Workers y Stackables.

Por increíble que parezca, es totalmente cierto. Hoy en día, PHP puede ejecutar múltiples subprocesos para aquellos que deseen probarlo.

La primera versión de PHP4, el 22 de mayo de 2000, PHP se envió con una arquitectura segura para subprocesos, una forma de ejecutar múltiples instancias de su intérprete en subprocesos separados en entornos SAPI (API de servidor) de subprocesos múltiples. Durante los últimos 13 años, el diseño de esta arquitectura se ha mantenido y avanzado: desde entonces se ha utilizado en producción en los sitios web más grandes del mundo.

Los subprocesos en el territorio de los usuarios nunca fueron una preocupación para el equipo de PHP, y lo siguen siendo hoy en día. Debe comprender que en el mundo donde PHP hace su negocio, ya existe un método definido de escalamiento: agregar hardware. A lo largo de los muchos años de existencia de PHP, el hardware se ha vuelto cada vez más barato, por lo que esto se volvió cada vez menos una preocupación para el equipo de PHP. Si bien se estaba volviendo más barato, también se volvió mucho más poderoso; Hoy en día, nuestros teléfonos móviles y tabletas tienen arquitecturas de dos y cuatro núcleos y mucha RAM, nuestras computadoras de escritorio y servidores suelen tener 8 o 16 núcleos, 16 y 32 gigabytes de RAM, aunque es posible que no siempre podamos tener dos. dentro del presupuesto y tener dos computadoras de escritorio rara vez es útil para la mayoría de nosotros.

Además, PHP fue escrito para quienes no son programadores; es la lengua nativa de muchos aficionados. La razón por la que PHP se adopta tan fácilmente es porque es un lenguaje fácil de aprender y escribir. La razón por la que PHP es tan confiable hoy en día es por la gran cantidad de trabajo que se dedica a su diseño y a cada decisión tomada por el grupo PHP. Su confiabilidad y grandeza lo mantienen en el centro de atención, después de todos estos años; donde sus rivales han caído por el tiempo o la presión.

La programación multiproceso no es fácil para la mayoría, incluso con la API más coherente y confiable, hay diferentes cosas en las que pensar y muchos conceptos erróneos. El grupo PHP no desea que el multithreading del usuario sea una característica central, nunca se le ha prestado mucha atención, y con razón. PHP no debería ser complejo, para todos.

A fin de cuentas, todavía se pueden obtener beneficios al permitir que PHP utilice sus características probadas y listas para producción para permitir un medio de aprovechar al máximo lo que tenemos, cuando agregar más no siempre es una opción, y para muchos de tareas nunca es realmente necesario.

pthreads logra, para aquellos que deseen explorarlo, una API que permite al usuario utilizar múltiples subprocesos en aplicaciones PHP. Su API es en gran medida un trabajo en progreso y está designada como un nivel beta de estabilidad e integridad.

Es de conocimiento común que algunas de las bibliotecas que utiliza PHP no son seguras para subprocesos; el programador debe tener claro que pthreads no puede cambiar esto y no intenta intentarlo. Sin embargo, cualquier biblioteca que sea segura para subprocesos se puede utilizar, como en cualquier otra configuración del intérprete segura para subprocesos.

pthreads utiliza subprocesos Posix (incluso en Windows), lo que el programador crea son subprocesos de ejecución reales, pero para que esos subprocesos sean útiles, deben conocer PHP: ser capaz de ejecutar código de usuario, compartir variables y permitir un medio de comunicación útil. (sincronización). Por lo tanto, cada hilo se crea con una instancia del intérprete, pero por diseño, su intérprete está aislado de todas las demás instancias del intérprete, al igual que los entornos de API de servidor multiproceso. pthreads intenta cerrar la brecha de una manera sensata y segura. Muchas de las preocupaciones del programador de subprocesos en C simplemente no existen para el programador de pthreads; por diseño, pthreads se copia en lectura y se copia en escritura (la RAM es barata), por lo que nunca dos instancias manipulan los mismos datos físicos. , pero ambos pueden afectar los datos de otro hilo. El hecho de que PHP pueda utilizar funciones inseguras para subprocesos en su programación principal es completamente irrelevante, los subprocesos de usuario y sus operaciones son completamente seguros.

Por qué copiar en lectura y copiar en escritura:

public function run() {
    ...
    (1) $this->data = $data;
    ...
    (2) $this->other = someOperation($this->data);
    ...
}

(3) echo preg_match($pattern, $replace, $thread->data);

(1) Mientras se mantienen bloqueos de lectura y escritura en el almacén de datos de objetos pthreads, los datos se copian desde su ubicación original en la memoria al almacén de objetos. pthreads no ajusta el recuento de la variable, Zend puede liberar los datos originales si no hay más referencias a ellos.

(2) El argumento de someOperation hace referencia al almacén de objetos, los datos originales almacenados, que en sí mismos son una copia del resultado de (1), se copian nuevamente para el motor en un contenedor zval, mientras esto ocurre, se mantiene un bloqueo de lectura. En el almacén de objetos, el bloqueo se libera y el motor puede ejecutar la función. Cuando se crea el zval, tiene un recuento de 0, lo que permite al motor liberar la copia al finalizar la operación, porque no existen otras referencias a él.

(3) El último argumento de preg_match hace referencia al almacén de datos, se obtiene un bloqueo de lectura, los datos establecidos en (1) se copian a un zval, nuevamente con un refcount de 0. El bloqueo se libera, la llamada a preg_match opera en una copia de los datos, que es en sí misma una copia de los datos originales.

Cosas que saber:

  • La tabla hash del almacén de objetos donde se almacenan los datos, segura para subprocesos, se
    basa en TsHashTable enviada con PHP, por Zend.

  • El almacén de objetos tiene un bloqueo de lectura y escritura, se proporciona un bloqueo de acceso adicional para TsHashTable de modo que, si es necesario (y lo hace, var_dump/print_r, acceso directo a las propiedades cuando el motor PHP quiere hacer referencia a ellas), los pthreads pueden manipular TsHashTable. fuera de la API definida.

  • Los bloqueos sólo se mantienen mientras se realizan las operaciones de copia, cuando se han realizado las copias los bloqueos se liberan, en un orden sensato.

Esto significa:

  • Cuando se produce una escritura, no solo se mantienen un bloqueo de lectura y escritura, sino también un bloqueo de acceso adicional. La tabla en sí está bloqueada, no hay forma posible de que otro contexto pueda bloquearla, leerla, escribirla o afectarla.

  • Cuando se produce una lectura, no solo se mantiene el bloqueo de lectura, sino también el bloqueo de acceso adicional; nuevamente, la tabla se bloquea.

No hay dos contextos que puedan acceder física ni simultáneamente a los mismos datos del almacén de objetos, pero las escrituras realizadas en cualquier contexto con una referencia afectarán los datos leídos en cualquier contexto con una referencia.

Esta es una arquitectura de nada compartido y la única forma de existir es coexistir. Aquellos un poco inteligentes verán que se están copiando mucho aquí y se preguntarán si eso es algo bueno. Se realizan muchas copias dentro de un tiempo de ejecución dinámico, esa es la dinámica de un lenguaje dinámico. pthreads se implementa en el nivel del objeto, porque se puede obtener un buen control sobre un objeto, pero los métodos (el código que ejecuta el programador) tienen otro contexto, libre de bloqueos y copias: el alcance del método local. El alcance del objeto en el caso de un objeto pthreads debe tratarse como una forma de compartir datos entre contextos, ese es su propósito. Con esto en mente, puede adoptar técnicas para evitar bloquear el almacén de objetos a menos que sea necesario, como pasar variables de alcance local a otros métodos en un objeto subproceso en lugar de copiarlos desde el almacén de objetos durante la ejecución.

La mayoría de las bibliotecas y extensiones disponibles para PHP son envoltorios finos de terceros, la funcionalidad principal de PHP hasta cierto punto es la misma. pthreads no es una envoltura delgada alrededor de Posix Threads; es una API de subprocesos basada en Posix Threads. No tiene sentido implementar subprocesos en PHP que sus usuarios no comprendan o no puedan usar. No hay ninguna razón por la que una persona sin conocimiento de qué es o qué hace un mutex no pueda aprovechar todo lo que tiene, tanto en términos de habilidades como de recursos. Un objeto funciona como un objeto, pero dondequiera que dos contextos colisionen, pthreads proporciona estabilidad y seguridad.

Cualquiera que haya trabajado en Java verá las similitudes entre un objeto pthreads y los subprocesos en Java, esas mismas personas sin duda habrán visto un error llamado ConcurrentModificationException, ya que suena como un error generado por el tiempo de ejecución de Java si dos subprocesos escriben los mismos datos físicos. al mismo tiempo. Entiendo por qué existe, pero me desconcierta que con recursos tan baratos como son, junto con el hecho de que el tiempo de ejecución es capaz de detectar la concurrencia en el momento exacto y único en que se puede lograr la seguridad para el usuario, que elija arrojar un error posiblemente fatal en tiempo de ejecución en lugar de administrar la ejecución y el acceso a los datos.

pthreads no emitirá errores tan estúpidos; creo que la API está escrita para hacer que los subprocesos sean lo más estables y compatibles posible.

El uso de subprocesos múltiples no es como usar una nueva base de datos; se debe prestar mucha atención a cada palabra del manual y a los ejemplos incluidos con pthreads.

Por último, del manual de PHP:

pthreads fue, y es, un experimento con resultados bastante buenos. Cualquiera de sus limitaciones o características puede cambiar en cualquier momento; esa es la naturaleza de la experimentación. Sus limitaciones, a menudo impuestas por la implementación, existen por una buena razón; El objetivo de pthreads es proporcionar una solución utilizable para realizar múltiples tareas en PHP en cualquier nivel. En el entorno en el que se ejecuta pthreads, son necesarias algunas restricciones y limitaciones para proporcionar un entorno estable.

Joe Watkins avatar Jan 27 '2013 15:01 Joe Watkins

A continuación se muestra un ejemplo de lo que sugirió Wilco:

$cmd = 'nohup nice -n 10 /usr/bin/php -c /path/to/php.ini -f /path/to/php/file.php action=generate var1_id=23 var2_id=35 gen_id=535 > /path/to/log/file.log & echo $!';
$pid = shell_exec($cmd);

Básicamente, esto ejecuta el script PHP en la línea de comando, pero inmediatamente devuelve el PID y luego se ejecuta en segundo plano. (El echo $! garantiza que no se devuelva nada más que el PID). Esto permite que su script PHP continúe o se cierre si lo desea. Cuando usé esto, redirigí al usuario a otra página, donde cada 5 a 60 segundos se realiza una llamada AJAX para verificar si el informe aún se está ejecutando. (Tengo una tabla para almacenar gen_id y el usuario con el que está relacionado). El script de verificación ejecuta lo siguiente:

exec('ps ' . $pid , $processState);
if (count($processState) < 2) {
     // less than 2 rows in the ps, therefore report is complete
}

Hay una breve publicación sobre esta técnica aquí: http://nsaunders.wordpress.com/2007/01/12/running-a-background-process-in-php/

Darryl Hein avatar Oct 17 '2008 02:10 Darryl Hein

No hay nada disponible que yo sepa. La mejor opción sería simplemente hacer que un script ejecute otro a través de CLI, pero eso es un poco rudimentario. Dependiendo de lo que intentes hacer y de lo complejo que sea, esta puede ser una opción o no.

Wilco avatar Oct 16 '2008 18:10 Wilco

En resumen: sí, hay subprocesos múltiples en php pero deberías usar multiprocesamiento en su lugar.

Información de fondo: subprocesos frente a procesos

Siempre hay un poco de confusión acerca de la distinción entre subprocesos y procesos, por lo que describiré ambos brevemente:

  • Un hilo es una secuencia de comandos que procesará la CPU. Los únicos datos que lo componen son un contador de programa. Cada núcleo de CPU solo procesará un subproceso a la vez, pero puede alternar entre la ejecución de diferentes mediante programación.
  • Un proceso es un conjunto de recursos compartidos. Eso significa que consta de una parte de memoria, variables, instancias de objetos, identificadores de archivos, mutex, conexiones de bases de datos, etc. Cada proceso también contiene uno o más subprocesos. Todos los hilos del mismo proceso comparten sus recursos, por lo que puedes usar una variable en un hilo que creaste en otro. Si esos subprocesos son parte de dos procesos diferentes, entonces no pueden acceder directamente a los recursos del otro. En este caso, necesita comunicación entre procesos a través de, por ejemplo, tuberías, archivos, sockets...

Multiprocesamiento

Puedes lograr computación paralela creando nuevos procesos (que también contengan un nuevo hilo) con php. Si tus hilos no necesitan mucha comunicación o sincronización, esta es tu elección, ya que los procesos están aislados y no pueden interferir con el trabajo de los demás. Incluso si uno falla, eso no importa a los demás. Si necesita mucha comunicación, debería seguir leyendo en "multithreading" o, lamentablemente, considerar usar otro lenguaje de programación, porque la comunicación y sincronización entre procesos introduce mucha complejidad.

En php tienes dos formas de crear un nuevo proceso:

deje que el sistema operativo lo haga por usted : puede indicarle a su sistema operativo que cree un nuevo proceso y ejecute un nuevo (o el mismo) script php en él.

  • para Linux puede utilizar lo siguiente o considerar la respuesta de Darryl Hein :

    $cmd = 'nice php script.php 2>&1 & echo $!';
    pclose(popen($cmd, 'r'));
    
  • para windows puedes usar esto:

    $cmd = 'start "processname" /MIN /belownormal cmd /c "script.php 2>&1"';
    pclose(popen($cmd, 'r'));
    

Hágalo usted mismo con un fork : php también ofrece la posibilidad de utilizar el fork a través de la función pcntl_fork() . Puede encontrar un buen tutorial sobre cómo hacer esto aquí , pero recomiendo encarecidamente no usarlo, ya que fork es un crimen contra la humanidad y especialmente contra oop.

subprocesos múltiples

Con el subproceso múltiple, todos sus subprocesos comparten sus recursos para que pueda comunicarse fácilmente y sincronizarlos sin mucha sobrecarga. Por otro lado, debes saber lo que estás haciendo, ya que las condiciones de carrera y los puntos muertos son fáciles de producir pero muy difíciles de depurar.

PHP estándar no proporciona subprocesos múltiples, pero existe una extensión (experimental) que realmente sí lo hace: pthreads . Su documentación API incluso llegó a php.net . Con él puedes hacer algunas cosas que puedes hacer en lenguajes de programación reales :-) como esta:

class MyThread extends Thread {
    public function run(){
        //do something time consuming
    }
}

$t = new MyThread();
if($t->start()){
    while($t->isRunning()){
        echo ".";
        usleep(100);
    }
    $t->join();
}

Para Linux hay una guía de instalación aquí en stackoverflow.

Para Windows hay uno ahora:

  • Primero necesitas la versión segura para subprocesos de php.
  • Necesita las versiones precompiladas de pthreads y su extensión php. Se pueden descargar aquí . Asegúrese de descargar la versión que sea compatible con su versión de PHP.
  • Copie php_pthreads.dll (del zip que acaba de descargar) en su carpeta de extensión php ([phpDirectory]/ext).
  • Copie pthreadVC2.dll en [phpDirectory] (la carpeta raíz, no la carpeta de extensión).
  • Edite [phpDirectory]/php.ini e inserte la siguiente línea

    extension=php_pthreads.dll
    
  • Pruébelo con el script anterior con un poco de sueño o algo justo ahí donde está el comentario.

Y ahora el gran PERO : aunque esto realmente funciona, php no se creó originalmente para subprocesos múltiples. Existe una versión de php segura para subprocesos y, a partir de v5.4, parece estar casi libre de errores, pero todavía se desaconseja el uso de php en un entorno de subprocesos múltiples en el manual de php (pero tal vez simplemente no actualizaron su manual en esto, todavía). Un problema mucho mayor podría ser que muchas extensiones comunes no son seguras para subprocesos . Por lo tanto, es posible que obtenga subprocesos con esta extensión de PHP, pero las funciones de las que depende aún no son seguras para subprocesos, por lo que probablemente encontrará condiciones de carrera, interbloqueos, etc. en un código que no escribió usted mismo...

Francois Bourgeois avatar Jan 07 '2013 18:01 Francois Bourgeois

Puedes usar pcntl_fork() para lograr algo similar a los subprocesos. Técnicamente son procesos separados, por lo que la comunicación entre los dos no es tan simple con los subprocesos, y creo que no funcionará si Apache llama a PHP.

davr avatar Oct 16 '2008 22:10 davr