¿PHP tiene subprocesos?

130

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

Thomas Owens
fuente
Alguien sabe si esto ( pcntl_fork()) funcionará si se llama desde Apache?
Josh K
Esto es increíblemente antiguo, pero tengo una respuesta que en realidad proporciona subprocesos en php (consulte los enlaces a continuación).
Alec Gorge
Recomiendan no llamar a fork desde un entorno de servidor. No los culpo. Sin embargo, pcntl_fork parece ser la mejor solución para enhebrar PHP.
just_wes
Sí, no deberías necesitar bifurcar un proceso apache2 php.
andho
2
Usar pthreads funciona como encanto
Baba

Respuestas:

40

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

Wilco
fuente
1
Es lo que pensaba. Vi un montón de publicaciones antiguas diciendo que no, y nada en php.net, así que este fue mi pensamiento. Gracias por confirmarlo.
Thomas Owens,
2
Sí, ese paquete PECL es una especie de provocación, también me encontré con él, pero nunca salió nada.
Wilco
180

Del manual de PHP para la extensión pthreads :

pthreads es una API orientada a objetos que permite múltiples subprocesos 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 subprocesos, trabajadores y apilables.

Por increíble que parezca, es completamente cierto. Hoy, PHP puede realizar 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. En 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.

Subprocesos en la tierra de los usuarios nunca fue una preocupación para el equipo de PHP, y sigue siendo así hoy en día. Debe comprender que en el mundo donde PHP hace negocios, ya existe un método definido de escalado: agregar hardware. Durante los muchos años que ha existido PHP, el hardware se ha vuelto cada vez más barato y, por lo tanto, esto se convirtió cada vez menos en una preocupación para el equipo de PHP. Mientras se hacía más barato, también se volvía mucho más poderoso; Hoy en día, nuestros teléfonos móviles y tabletas tienen arquitecturas de doble y cuádruple núcleo y mucha RAM, nuestros equipos de escritorio y servidores comúnmente tienen 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 escritorios rara vez es útil para la mayoría de nosotros.

Además, PHP fue escrito para el no programador, 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 es por la gran cantidad de trabajo que implica su diseño y cada decisión tomada por el grupo PHP. Su confiabilidad y grandeza lo mantienen en el punto de mira, después de todos estos años; donde sus rivales han caído en el tiempo o la presión.

La programación de subprocesos múltiples 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 los usuarios de múltiples subprocesos sean 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 hay beneficios al permitir que PHP utilice sus características listas para producción y probadas para permitir un medio de aprovechar al máximo lo que tenemos, cuando agregar más no siempre es una opción, y por mucho de tareas nunca es realmente necesario.

pthreads logra, para aquellos que desean explorarlo, una API que permite a un usuario realizar múltiples aplicaciones PHP. Su API es en gran medida un trabajo en progreso y designó un nivel beta de estabilidad e integridad.

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

pthreads utiliza hilos Posix (incluso en Windows), lo que el programador crea son hilos reales de ejecución, pero para que esos hilos sean útiles, deben tener en cuenta PHP: capaz de ejecutar código de usuario, compartir variables y permitir un medio útil de comunicación (sincronización). Por lo tanto, cada subproceso 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 sana y segura. Muchas de las preocupaciones del programador de subprocesos en C simplemente no están ahí para el programador de pthreads, por diseño, pthreads es copiar al leer y copiar al escribir (la RAM es barata), por lo que no hay dos instancias que manipulen los mismos datos físicos. , pero ambos pueden afectar los datos en otro hilo.

Por qué copiar al leer y copiar al escribir:

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

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

(1) Mientras se mantiene un bloqueo de lectura y escritura en el almacén de datos del objeto 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 ella.

(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, se libera el bloqueo y el motor puede ejecutar la función. Cuando se crea el zval, tiene un recuento de 0, lo que permite que el motor libere la copia al finalizar la operación, porque no existen otras referencias a él.

(3) El último argumento para 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 recuento de 0. El bloqueo se libera, la llamada a preg_match funciona una copia de datos, que es en sí misma una copia de los datos originales.

Cosas que saber:

  • La tabla hash de la tienda de objetos donde se almacenan los datos, segura para subprocesos, se
    basa en TsHashTable enviado 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 como el motor PHP quiere hacer referencia a ellos) pthreads pueden manipular TsHashTable fuera de la API definida.

  • Los bloqueos solo se mantienen mientras se realizan las operaciones de copia, cuando se han realizado las copias, los bloqueos se liberan, en un orden razonable.

Esto significa:

  • Cuando se produce una escritura, no solo se mantiene un bloqueo de lectura y escritura, sino 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 o simultáneamente a los mismos datos del almacén de objetos, pero las escrituras hechas en cualquier contexto con una referencia afectarán los datos leídos en cualquier contexto con una referencia.

Esta arquitectura no comparte nada y la única forma de existir es coexistir. Aquellos un poco conocedores verán que hay muchas copias aquí, y se preguntarán si eso es algo bueno. Se realiza una gran cantidad de copias dentro de un tiempo de ejecución dinámico, esa es la dinámica de un lenguaje dinámico. pthreads se implementa a 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,

La mayoría de las bibliotecas y extensiones disponibles para PHP son envoltorios delgados de terceros, la funcionalidad básica de PHP en cierta medida es lo mismo. pthreads no es una envoltura delgada alrededor de los hilos Posix; Es una API de subprocesos basada en subprocesos Posix. No tiene sentido implementar Threads en PHP que los usuarios no entiendan o no puedan usar. No hay razón para que una persona sin conocimiento de lo que es o no sea un mutex no debería poder aprovechar todo lo que tiene, tanto en términos de habilidad como de recursos. Un objeto funciona como un objeto, pero donde dos contextos chocarían, pthreads proporciona estabilidad y seguridad.

Cualquiera que haya trabajado en Java verá las similitudes entre un objeto pthreads y threading en Java, esas mismas personas sin duda habrán visto un error llamado ConcurrentModificationException, ya que suena un error provocado por el tiempo de ejecución de Java si dos hilos escriben los mismos datos físicos concurrentemente 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 puede detectar la concurrencia en el momento exacto y único en que se puede lograr la seguridad para el usuario, que elige lanzar un error posiblemente fatal en tiempo de ejecución en lugar de administrar la ejecución y el acceso a los datos.

No se emitirán tales errores estúpidos por pthreads, la API está escrita para hacer que el subproceso sea lo más estable y compatible posible, creo.

Multi-threading no es como usar una nueva base de datos, se debe prestar mucha atención a cada palabra en el manual y los ejemplos enviados con pthreads.

Por último, del manual de PHP:

pthreads fue, y es, un experimento con muy buenos resultados. 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 útil para la multitarea en PHP a cualquier nivel. En el entorno que ejecuta pthreads, algunas restricciones y limitaciones son necesarias para proporcionar un entorno estable.

Joe Watkins
fuente
14
Absoluto sin sentido.
Joe Watkins
77
@GeoC. Ni siquiera estoy seguro de cuál es su punto aquí, eso es solo una carga de galimatías y no proporciona razones , lógicas o de otro tipo, de por qué no está de acuerdo con ningún argumento (del cual realmente no puedo ver ninguno en la publicación) .
Jimbo
13
@Tudor No creo que realmente sepas de lo que estás hablando, así que estoy feliz de ignorarte.
Joe Watkins
44
@Tudor: muchos científicos no "vieron el punto" de algo nuevo en su rama, o algo útil. La historia ha demostrado que la mayoría de las veces, personas como usted simplemente están equivocadas, es un hecho. Al igual que es un hecho que todo lo que escribiste aquí es, a falta de una mejor palabra, heces. Sugiero encarecidamente, con todo lo positivo en mente, que no participe en temas de los que no sabe nada (este es uno).
NB
12
Cosa graciosa. Joe Watkins es el autor de pthreads, y aún Tudor intenta demostrar que está equivocado.
Hristo Valkanov
48

Aquí hay 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 $! Asegura 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 utilicé 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 el 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
fuente
Tengo un pequeño problema, ¿cómo verificas el estado del proceso en segundo plano? ¿Puedes iluminarme?
Slier
25

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

Información de fondo: hilos vs. procesos

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

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

Multiprocesamiento

Puede lograr la computación paralela creando nuevos procesos (que también contienen un nuevo hilo) con php. Si sus hilos no necesitan mucha comunicación o sincronización, esta es su elección, ya que los procesos están aislados y no pueden interferir con el trabajo del otro. Incluso si uno falla, eso no le concierne a los demás. Si necesita mucha comunicación, debe seguir leyendo en "subprocesos múltiples" 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 usar 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 una bifurcación : php también ofrece la posibilidad de utilizar la bifurcación a través de la función pcntl_fork () . Aquí se puede encontrar un buen tutorial sobre cómo hacerlo, pero recomiendo encarecidamente no usarlo, ya que fork es un crimen contra la humanidad. y especialmente contra oop.

Multithreading

Con el subprocesamiento múltiple, todos sus subprocesos comparten sus recursos para que pueda comunicarse y sincronizarlos fácilmente 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.

El php estándar no proporciona ningún subproceso múltiple, pero hay una extensión (experimental) que realmente lo hace: pthreads . Su documentación de API incluso llegó a php.net . Con él puedes hacer algunas cosas como puedes en lenguajes de programación reales :-) como este:

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's.

Para windows hay uno ahora:

  • Primero necesitas la versión de php segura para subprocesos.
  • Necesita las versiones precompiladas de ambos 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 allí donde está el comentario.

Y ahora el gran PERO : aunque esto realmente funciona, php no fue creado originalmente para multithreading. Existe una versión de php segura para subprocesos y, a partir de v5.4, parece estar casi libre de errores, pero el uso de php en un entorno multiproceso aún no se recomienda 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 hilos con esta extensión php, pero las funciones de las que depende todavía no son seguras para los hilos, por lo que probablemente encontrará condiciones de carrera, puntos muertos, etc.

Francois Bourgeois
fuente
55
Eso es terriblemente incorrecto, el artículo al que hizo referencia es de 2008. Si PHP no fuera seguro para subprocesos en el núcleo, no habría enhebrado los módulos SAPI.
Joe Watkins el
1
@ Joe: Muy bien, lo cambié en el núcleo es seguro para subprocesos, pero muchas extensiones no lo son.
Francois Bourgeois
1
Un montón ? Creo que encontrará que son muy pocos, ha encontrado la documentación pero no pudo leerla correctamente: Nota: los marcados con * no son bibliotecas seguras para subprocesos, y no deben usarse con PHP como un módulo de servidor en el multi servidores web de Windows con hilos (IIS, Netscape). Esto no importa en entornos Unix, todavía.
Joe Watkins el
66
PHP es muy seguro para subprocesos, y lo ha sido durante muchos años, algunas de las bibliotecas externas y un par de paquetes no lo son, pero está bien documentado y es bastante obvio de todos modos. pthreads crea hilos que son tan seguros como los hilos creados por zend en un sapi multihilo, lo sé porque yo solo escribí pthreads. Utiliza todas las API disponibles expuestas por PHP al igual que las API del servidor, no estoy diciendo que sea completamente estable, pero la imagen que ha pintado es simplemente incorrecta y está muy mal informada.
Joe Watkins el
@ Joe: Cuando el manual dice que esto no importa para los entornos Unix, se refieren al hecho de que en el sistema Unix apache usa múltiples procesos y en Windows usa hilos. Así que, básicamente, dicen "si no estás usando hilos de todos modos, no tienes que preocuparte por extensiones que no sean seguras para hilos". Cuando usamos hilos con pthreads, por supuesto, también importa en entornos Unix.
Francois Bourgeois
17

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

davr
fuente
44
Estoy usando con éxito pcntl_fork para paralelizar una tarea de importación de datos bastante masiva. Funciona muy bien, y lo tuve funcionando en aproximadamente una hora. Hay una pequeña curva de aprendizaje, pero una vez que entiendes lo que está sucediendo, es bastante sencillo.
Frank Farmer
Frank, ¿es eso con CLI php o apache PHP?
Artem Russakovskii
@Artem: Me gustaría saber también.
Josh K
55
@Frank Farmer nos está tomando el pelo ... al igual que el paquete PECL.
Roger
1
Estaba usando pcntl_fork con CLI. Nunca lo he probado en apache; Eso suena arriesgado. Incluso en CLI, hubo algunos problemas inesperados. Parecía estar teniendo un problema en el que si un niño cerraba un identificador de base de datos (porque terminó su trabajo), también cerraba la conexión para los hermanos. Como los niños son copias de los padres, prepárese para la rareza. Desde entonces, rediseñé mi código para generar procesos nuevos y completamente separados a través de exec (), de esa manera es más limpio.
Frank Farmer
7

pcntl_fork()es lo que está buscando, pero su proceso no se bifurca. entonces tendrá el problema del intercambio de datos. para resolverlos, puede usar las colas de mensajes de las funciones de semáforo phps ( http://www.php.net/manual/de/ref.sem.php ) pueden ser un poco más fáciles al principio que los segmentos de memoria compartida.

De todos modos, una estrategia que estoy usando en un marco web que estoy desarrollando que carga bloques de una página web que requieren muchos recursos (probablemente con solicitudes externas) en paralelo: estoy haciendo una cola de trabajo para saber qué datos estoy esperando y luego bifurco fuera de los trabajos para cada proceso. Una vez hecho esto, almacenan sus datos en el caché de la APC bajo una clave única a la que puede acceder el proceso principal. Una vez que todos los datos están allí, continúan. Estoy usando simpleusleep() para esperar porque la comunicación entre procesos no es posible en apache (los niños perderán la conexión con sus padres y se convertirán en zombies ...). así que esto me lleva a lo último: ¡es importante auto matar a cada niño! también hay clases que bifurcan procesos pero conservan datos, no los examiné, pero zend framework tiene uno, y generalmente hacen un código lento pero confiable. Lo puedes encontrar aquí: http://zendframework.com/manual/1.9/en/zendx.console.process.unix.overview.html ¡ Creo que usan segmentos shm! bueno, por último, pero no menos importante, hay un error en este sitio web zend, un error menor en el ejemplo.

while ($process1->isRunning() && $process2->isRunning()) {
    sleep(1);
}
should of course be:
while ($process1->isRunning() || $process2->isRunning()) {
    sleep(1);
}
El surrican
fuente
5

Tengo una clase de subprocesamiento de PHP que se ha ejecutado sin problemas en un entorno de producción durante más de dos años.

EDITAR: ahora está disponible como una biblioteca de compositores y como parte de mi marco MVC, Hazaar MVC.

Ver: https://git.hazaarlabs.com/hazaar/hazaar-thread

Jamie Carl
fuente
¿Qué sucede si, siguiendo su ejemplo, el programa en file.php, digamos, por ejemplo, corrige la existencia de una lista de uris de sitios web de 10k y luego tiene que guardar el resultado en un archivo CSV ... Esta escritura de archivo sería un ¿problema?
Roger
El subproceso se ejecutará como el mismo usuario que el script del servidor web / padre. Entonces, al escribir archivos, tendrá las mismas consideraciones con respecto a los permisos que lo haría normalmente. Si tiene problemas para escribir archivos, intente escribir en / tmp y, cuando esté funcionando, vaya desde allí.
Jamie Carl
1
El enlace ahora está muerto debido a un rediseño, puede obtenerlo en la máquina del camino aquí: web.archive.org/web/20130922043615/http://dev.funkynerd.com/…
Tony
Agregado a mi marco MVC ahora. Ver: git.hazaarlabs.com/hazaar/hazaar-thread
Jamie Carl
1

¿Has oído hablar appserverde TechDivision?

Está escrito en php y funciona como un servidor de aplicaciones que administra subprocesos múltiples para aplicaciones php de alto tráfico. Todavía está en beta pero es muy prometedor.

usuario2627170
fuente
-3

Existe la característica bastante oscura, y que pronto será desaprobada, llamada ticks . Para lo único que lo he usado es para permitir que un script capture SIGKILL (Ctrl + C) y se cierre con gracia.

Troelskn
fuente
3
Las garrapatas no se ejecutan en paralelo. Esencialmente, después de cada declaración, se ejecuta su función de marca. Mientras se ejecuta su función de marca, el código principal no se está ejecutando.
Frank Farmer
1
los ticks solo son necesarios para el controlador de señal ().
Nick