multitarea multihilos python multiprocessing

python - multihilos - multiprocesamiento: ¿compartir un gran objeto de solo lectura entre procesos?



multihilos en python (7)

¿Los procesos hijos creados a través de multiprocesamiento comparten objetos creados anteriormente en el programa?

Depende. Para las variables globales de solo lectura, a menudo se lo puede considerar así (aparte de la memoria consumida) de lo contrario no debería.

La documentación de multiprocessing dice:

Better to inherit than pickle/unpickle

En Windows, muchos tipos de multiprocesamiento deben ser seleccionables para que los procesos secundarios puedan usarlos. Sin embargo, generalmente se debe evitar el envío de objetos compartidos a otros procesos que usen tuberías o colas. En su lugar, debe organizar el programa de modo que un proceso que necesite acceso a un recurso compartido creado en otro lugar pueda heredarlo de un proceso ancestro.

Explicitly pass resources to child processes

En Unix, un proceso hijo puede hacer uso de un recurso compartido creado en un proceso principal utilizando un recurso global. Sin embargo, es mejor pasar el objeto como argumento al constructor para el proceso hijo.

Además de hacer que el código (potencialmente) sea compatible con Windows, esto también garantiza que mientras el proceso hijo aún esté activo, el objeto no será basura recolectada en el proceso principal. Esto podría ser importante si algún recurso se libera cuando el objeto es basura recolectada en el proceso principal.

Global variables

Tenga en cuenta que si el código ejecutado en un proceso secundario intenta acceder a una variable global, entonces el valor que ve (si lo hay) puede no ser el mismo que el valor en el proceso principal en el momento en que se llamó a Process.start () .

Ejemplo

En Windows (CPU única):

#!/usr/bin/env python import os, sys, time from multiprocessing import Pool x = 23000 # replace `23` due to small integers share representation z = [] # integers are immutable, let''s try mutable object def printx(y): global x if y == 3: x = -x z.append(y) print os.getpid(), x, id(x), z, id(z) print y if len(sys.argv) == 2 and sys.argv[1] == "sleep": time.sleep(.1) # should make more apparant the effect if __name__ == ''__main__'': pool = Pool(processes=4) pool.map(printx, (1,2,3,4))

Con sleep :

$ python26 test_share.py sleep 2504 23000 11639492 [1] 10774408 1 2564 23000 11639492 [2] 10774408 2 2504 -23000 11639384 [1, 3] 10774408 3 4084 23000 11639492 [4] 10774408 4

Sin sleep :

$ python26 test_share.py 1148 23000 11639492 [1] 10774408 1 1148 23000 11639492 [1, 2] 10774408 2 1148 -23000 11639324 [1, 2, 3] 10774408 3 1148 -23000 11639324 [1, 2, 3, 4] 10774408 4

¿Los procesos hijos creados a través de multiprocessing comparten objetos creados anteriormente en el programa?

Tengo la siguiente configuración:

do_some_processing(filename): for line in file(filename): if line.split('','')[0] in big_lookup_object: # something here if __name__ == ''__main__'': big_lookup_object = marshal.load(''file.bin'') pool = Pool(processes=4) print pool.map(do_some_processing, glob.glob(''*.data''))

Estoy cargando un objeto grande en la memoria, y luego creo un grupo de trabajadores que necesitan hacer uso de ese gran objeto. Se accede al objeto grande de solo lectura, no necesito pasar modificaciones entre los procesos.

Mi pregunta es: ¿se carga el gran objeto en la memoria compartida, como sería si generase un proceso en Unix / c, o cada proceso carga su propia copia del objeto grande?

Actualización: para aclarar aún más: big_lookup_object es un objeto de búsqueda compartido. No necesito dividirlo y procesarlo por separado. Necesito guardar una sola copia de eso. El trabajo que necesito para dividirlo es leer muchos otros archivos grandes y buscar los elementos en esos archivos grandes contra el objeto de búsqueda.

Actualización adicional: la base de datos es una buena solución, Memcached podría ser una mejor solución, y el archivo en disco (shelve o dbm) podría ser incluso mejor. En esta pregunta, estaba particularmente interesado en una solución de memoria. Para la solución final, utilizaré hadoop, pero quería ver si también puedo tener una versión local en memoria.


"¿Se generan procesos secundarios a través de multiprocesamiento de objetos compartidos creados anteriormente en el programa?"

No.

Los procesos tienen espacio de memoria independiente.

Solución 1

Para hacer el mejor uso de una gran estructura con muchos trabajadores, haz esto.

  1. Escriba cada trabajador como un "filtro": lee los resultados intermedios de stdin, funciona, escribe los resultados intermedios en stdout.

  2. Conecte todos los trabajadores como una tubería:

    process1 <source | process2 | process3 | ... | processn >result

Cada proceso lee, funciona y escribe.

Esto es notablemente eficiente ya que todos los procesos se ejecutan simultáneamente. Las escrituras y lecturas pasan directamente a través de búferes compartidos entre los procesos.

Solución 2

En algunos casos, tiene una estructura más compleja, a menudo una estructura de "despliegue". En este caso, tiene un padre con varios hijos.

  1. El padre abre datos de origen. Los padres bifurcan a varios niños.

  2. El padre lee la fuente y distribuye partes de la fuente a cada hijo que se ejecuta simultáneamente.

  3. Cuando el padre llega al final, cierre la tubería. El niño obtiene el final del archivo y termina normalmente.

Las partes del niño son agradables de escribir porque cada niño simplemente lee sys.stdin .

El padre tiene un poco de trabajo de pies elegante para engendrar a todos los niños y retener las tuberías correctamente, pero no está tan mal.

Fan-in es la estructura opuesta. Varios procesos que se ejecutan de forma independiente deben intercalar sus entradas en un proceso común. El recopilador no es tan fácil de escribir, ya que tiene que leer de muchas fuentes.

La lectura de muchas tuberías con nombre se hace a menudo usando el módulo de select para ver qué tuberías tienen entrada pendiente.

Solución 3

Búsqueda compartida es la definición de una base de datos.

Solución 3A: cargue una base de datos. Permita que los trabajadores procesen los datos en la base de datos.

Solución 3B: cree un servidor muy simple utilizando werkzeug (o similar) para proporcionar aplicaciones WSGI que respondan a HTTP GET para que los trabajadores puedan consultar el servidor.

Solución 4

Objeto del sistema de archivos compartido. El sistema operativo Unix ofrece objetos de memoria compartida. Estos son solo archivos que están mapeados en la memoria para que se realice el intercambio de E / S en lugar de más lecturas almacenadas en la convención.

Puedes hacerlo desde un contexto de Python de varias maneras

  1. Escriba un programa de inicio que (1) divida su objeto gigantesco original en objetos más pequeños, y (2) inicie trabajadores, cada uno con un objeto más pequeño. Los objetos más pequeños podrían ser objetos de Python en escabeche para ahorrar un poco de tiempo de lectura de archivos.

  2. Escriba un programa de inicio que (1) lea su objeto gigantesco original y escriba un archivo codificado por bytes estructurado en la página utilizando operaciones de seek para asegurar que las secciones individuales sean fáciles de encontrar con búsquedas simples. Esto es lo que hace un motor de base de datos: dividir los datos en páginas, hacer que cada página sea fácil de localizar a través de una seek .

    Los trabajadores de freza con acceso a este archivo de estructura de página grande. Cada trabajador puede buscar las partes relevantes y hacer su trabajo allí.



No está directamente relacionado con el multiprocesamiento per se, pero a partir de su ejemplo, parece que podría usar simplemente el módulo de shelve o algo así. ¿El "big_lookup_object" realmente tiene que estar completamente en la memoria?


Para la plataforma Linux / Unix / MacOS, forkmap es una solución rápida y sucia.


Si está ejecutando bajo Unix, pueden compartir el mismo objeto, debido a la forma en que funciona el fork (es decir, los procesos secundarios tienen memoria separada pero es copy-on-write, por lo que puede compartirse mientras nadie lo modifique). Intenté lo siguiente:

import multiprocessing x = 23 def printx(y): print x, id(x) print y if __name__ == ''__main__'': pool = multiprocessing.Pool(processes=4) pool.map(printx, (1,2,3,4))

y obtuve el siguiente resultado:

$ ./mtest.py 23 22995656 1 23 22995656 2 23 22995656 3 23 22995656 4

Por supuesto, esto no prueba que no se haya realizado una copia, pero debería poder verificarlo en su situación mirando el resultado de ps para ver cuánta memoria real está usando cada subproceso.


S.Lott es correcto. Los accesos directos de multiproceso de Python efectivamente le dan un trozo de memoria duplicado por separado.

En la mayoría de los sistemas * nix, el uso de una llamada de nivel inferior a os.fork() , de hecho, le dará la memoria de copiado por escritura, que podría ser lo que está pensando. AFAIK, en teoría, en el más simple de los programas posibles, podría leer esos datos sin tener que duplicarlos.

Sin embargo, las cosas no son tan simples en el intérprete de Python. Los datos de objeto y los metadatos se almacenan en el mismo segmento de memoria, por lo que incluso si el objeto nunca cambia, algo así como un contador de referencia para ese objeto que se incrementa provocará una escritura de memoria y, por lo tanto, una copia. Casi cualquier programa de Python que esté haciendo algo más que "imprimir ''hola''" causará incrementos en el recuento de referencias, por lo que es probable que nunca se dé cuenta del beneficio de copiar y escribir.

Incluso si alguien lograra piratear una solución de memoria compartida en Python, tratar de coordinar la recolección de basura en todos los procesos probablemente sería muy doloroso.