Sumar las ventas de tres tiendas
Un proceso es un programa en marcha con su propia memoria. Un hilo es una línea de trabajo dentro de un proceso, y comparte su memoria. Para repartir un cálculo se corta el trabajo en trozos y cada proceso toma uno.
Una cadena de tres tiendas suma sus ventas del mes. Cada tienda suma las suyas y al final se juntan los tres totales. Esa es la forma de casi todo trabajo paralelo.
Al final de esta lección
Nota
Puedo repartir un trabajo en trozos entre procesos y juntar sus resultados.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Proceso | un programa en marcha con memoria propia |
| Hilo | una línea de trabajo que comparte la memoria del proceso |
| Trozo | una parte contigua de los datos, para un trabajador |
| Resultado parcial | lo que calcula un trabajador con su trozo |
| Combinar | juntar los resultados parciales en uno final |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un hilo comparte la memoria del proceso
Dos procesos no ven los datos del otro: se los pasan por mensajes. Eso evita pisarse, pero copiar datos cuesta.
Dos hilos del mismo proceso ven los mismos datos, y pueden estropearlos si escriben a la vez. En el Python habitual, un cálculo largo repartido en hilos no se acelera. Por eso en esta lección se usan procesos.
Cortar los datos en trozos contiguos
Un trozo es una parte seguida de la lista. Con 12 días y 3 trabajadores, cada uno toma 4 días seguidos. El primero toma los días 0 a 3, el segundo del 4 al 7 y el tercero del 8 al 11.
Cada uno calcula su resultado parcial sin saber nada de los demás. Al final se combinan los tres. Eso solo funciona si cada trozo se puede calcular por separado.
Cortar las ventas y sumar cada trozo
# Ventas de 12 días, en euros. Se reparten en 3 trozos de 4 días.
ventas = [120, 80, 95, 110, 130, 70, 60, 150, 90, 100, 85, 105]
trozos = [ventas[0:4], ventas[4:8], ventas[8:12]]
parciales = []
for trozo in trozos:
parciales.append(sum(trozo))
print("Sumas parciales:", parciales)
print("Total juntando los trozos:", sum(parciales))
print("Total de una sola vez:", sum(ventas))
# Cambia el segundo 70 por 700 y vuelve a ejecutar: ¿siguen siendo iguales?
Un grupo de procesos reparte los trozos
En Python, el módulo multiprocessing trae Pool, un grupo de procesos trabajadores. grupo.map(funcion, trozos) manda cada trozo a un proceso y devuelve los resultados.
Los resultados llegan en el orden de los trozos, aunque un proceso termine antes que otro. Por eso el resultado es siempre el mismo.
Un grupo de dos procesos
from multiprocessing import Pool
def partial_sum(chunk):
return sum(chunk)
if __name__ == "__main__":
ventas = [120, 80, 95, 110, 130, 70, 60, 150]
trozos = [ventas[0:4], ventas[4:8]]
with Pool(2) as grupo: # dos procesos trabajadores
parciales = grupo.map(partial_sum, trozos)
print(parciales) # [405, 410], en el orden de los trozos
Qué imprime el script del laboratorio
El laboratorio trae steps/chunks.py. Corta los números del 1 al 40 en cuatro trozos de 10 y los reparte entre dos procesos. Imprime la suma de cada trozo, la suma total de las sumas parciales y la suma de todos los números de una vez.
Las palabras que imprime están en inglés, como chunk y partial sum. Las dos sumas finales coinciden.
Repartir 1 a 40 entre dos procesos
docker exec jupyter python3 steps/chunks.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ver un trozo de una lista
docker exec jupyter python3 -c "print(list(range(1, 41))[10:20])"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
No todo se combina sumando
Para una suma, juntar parciales es sumar. Para un máximo, es el mayor de los máximos. Para contar palabras, es sumar los contadores de cada una.
Para una media, no basta con promediar las medias. Hay que juntar las sumas y las cantidades, y dividir al final.
La media de las medias se equivoca
# Dos trozos de notas: [10, 20] y [30]. ¿Cuál es la media de las tres notas?
trozo_a = [10, 20]
trozo_b = [30]
media_a = sum(trozo_a) / len(trozo_a)
media_b = sum(trozo_b) / len(trozo_b)
print("Media de las medias:", (media_a + media_b) / 2)
print("Media real:", (sum(trozo_a) + sum(trozo_b)) / (len(trozo_a) + len(trozo_b)))
# Lo correcto es juntar sumas y cantidades, no medias.
Un error muy común
Advertencia
Mucha gente cree que la media de las medias es la media de todo. Solo lo es si todos los trozos tienen el mismo tamaño. Guarda de cada trozo su suma y su cantidad, júntalas y divide una sola vez al final.