Marcombo / python-a-fondo

Libro: Python a fondo | Autor: Óscar Ramirez

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Liquidar el pool antes de terminar

chemacortes opened this issue · comments

Sin ver el libro, sólo con el código, diría que en en la función multiproceso para cálcular números primos se matarían los procesos antes de que acaben, falseando los tiempos de ejecución:

Hola @chemacortes, gracias por tu comentario, pero en este caso no afecta a la ejecución, a las explicaciones del libro o a los tiempo medidos.

Para poder invocar a la función join() del Pool de conexiones, es necesario que el pool esté en estado terminate o closed, habiendo llamado a las funciones close() o terminate() https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.Pool.join.

La función terminate(), no mata a los procesos que ya están corriendo, sino cuando salta el recolector de basura, y por lo tanto no afecta en este caso particular el hacer uso de terminate() o de close(), pero sí que es necesario llamar a alguno de ellos antes de llamar a join().

He comprobado y extraído los tiempos usando terminate y close, y se puede comprobar a continuación, los tiempos son los mismos, por tanto no hay cambios en ellos (ni falseamiento de los mismos):

-- Using terminate()
2021-01-29 08:37:26,156 - INFO - Multi_proc - 1 iter, 4 procesos: 2.312s
2021-01-29 08:37:28,391 - INFO - Multi_proc - 1 iter, 8 procesos: 2.235s
2021-01-29 08:37:30,824 - INFO - Multi_proc - 1 iter, 16 procesos: 2.433s
2021-01-29 08:37:33,318 - INFO - Multi_proc - 1 iter, 20 procesos: 2.493s
2021-01-29 08:37:35,819 - INFO - Multi_proc - 4 iter, 4 procesos: 2.5s
2021-01-29 08:37:38,536 - INFO - Multi_proc - 4 iter, 8 procesos: 2.717s
2021-01-29 08:37:41,232 - INFO - Multi_proc - 4 iter, 16 procesos: 2.696s
2021-01-29 08:37:44,044 - INFO - Multi_proc - 4 iter, 20 procesos: 2.812s
2021-01-29 08:37:51,451 - INFO - Multi_proc - 10 iter, 4 procesos: 7.407s
2021-01-29 08:37:59,105 - INFO - Multi_proc - 10 iter, 8 procesos: 7.654s
2021-01-29 08:38:06,032 - INFO - Multi_proc - 10 iter, 16 procesos: 6.927s
2021-01-29 08:38:13,077 - INFO - Multi_proc - 10 iter, 20 procesos: 7.044s
2021-01-29 08:38:27,437 - INFO - Multi_proc - 20 iter, 4 procesos: 14.36s
2021-01-29 08:38:40,666 - INFO - Multi_proc - 20 iter, 8 procesos: 13.23s
2021-01-29 08:38:55,800 - INFO - Multi_proc - 20 iter, 16 procesos: 15.13s
2021-01-29 08:39:09,703 - INFO - Multi_proc - 20 iter, 20 procesos: 13.9s

-- Using close()
2021-01-29 08:39:56,412 - INFO - Multi_proc - 1 iter, 4 procesos: 2.276s
2021-01-29 08:39:58,676 - INFO - Multi_proc - 1 iter, 8 procesos: 2.264s
2021-01-29 08:40:01,117 - INFO - Multi_proc - 1 iter, 16 procesos: 2.44s
2021-01-29 08:40:03,706 - INFO - Multi_proc - 1 iter, 20 procesos: 2.589s
2021-01-29 08:40:06,293 - INFO - Multi_proc - 4 iter, 4 procesos: 2.587s
2021-01-29 08:40:08,708 - INFO - Multi_proc - 4 iter, 8 procesos: 2.415s
2021-01-29 08:40:11,296 - INFO - Multi_proc - 4 iter, 16 procesos: 2.587s
2021-01-29 08:40:13,976 - INFO - Multi_proc - 4 iter, 20 procesos: 2.68s
2021-01-29 08:40:20,403 - INFO - Multi_proc - 10 iter, 4 procesos: 6.427s
2021-01-29 08:40:27,752 - INFO - Multi_proc - 10 iter, 8 procesos: 7.349s
2021-01-29 08:40:34,493 - INFO - Multi_proc - 10 iter, 16 procesos: 6.741s
2021-01-29 08:40:42,220 - INFO - Multi_proc - 10 iter, 20 procesos: 7.726s
2021-01-29 08:40:56,667 - INFO - Multi_proc - 20 iter, 4 procesos: 14.45s
2021-01-29 08:41:09,945 - INFO - Multi_proc - 20 iter, 8 procesos: 13.28s
2021-01-29 08:41:24,785 - INFO - Multi_proc - 20 iter, 16 procesos: 14.84s
2021-01-29 08:41:38,837 - INFO - Multi_proc - 20 iter, 20 procesos: 14.05s

De todas formas, dado que puede crear confusión, lo cambiaré en siguientes versiones y/o ediciones para usar close() y que quede más claro.
Gracias por el feedback y por comprobar el código.

La función terminate(), no mata a los procesos que ya están corriendo, sino cuando salta el recolector de basura, y por lo tanto no afecta en este caso particular el hacer uso de terminate() o de close(), pero sí que es necesario llamar a alguno de ellos antes de llamar a join().

Creo que no es así. terminate() para los workers del pool por completo. En el código que pones de ejemplo no se nota porque el método map() bloquea el proceso principal hasta que se obtienen todos los resultados, razón por la que no ves problema en usar terminate() en lugar de close().

He cambiado algo el código para usar imap en su lugar. Después de usar terminate, los procesos ya no responden si intentas obtener el resultado.

En cuanto al recolector de basura, nunca va a actuar sobre los procesos mientras sigan referenciandos por el pool, o sea, hasta que se sale de la función en este caso:

from multiprocessing import TimeoutError as ProcessTimeoutError
from multiprocessing import active_children

def contar_primos2(n_primos):
    primos_encontrados = []
    valor_actual = 2
    while len(primos_encontrados) < n_primos:
        if es_primo(valor_actual):
            # logger.debug(f'Primo {valor_actual} encontrado por: {n_iter}')
            primos_encontrados.append(valor_actual)
        valor_actual += 1
    return primos_encontrados

def ejecucion_multiproceso2(iteraciones, n_procesos, n_primos):
    p = Pool(n_procesos)
    params = [n_primos for _ in range(iteraciones)]
    it = p.imap(contar_primos2, params)
    logger.info(f"procesos activos (A): {len(active_children())}")

    # damos tiempo para que termine algún proceso
    time.sleep(2)

    # <<<<<<<<
    #p.close()
    p.terminate()
    # >>>>>>>>

    logger.info(f"procesos activos (B): {len(active_children())}")
    p.join()
    logger.info(f"procesos activos (C): {len(active_children())}")

    result = []
    while 1:
        try:
            res = it.next(0.3)
            result.append(res)
        except StopIteration:
            break
        except ProcessTimeoutError:
            logger.info("TimeoutError")
            break

    return (len(result), [len(res) for res in result])

if __name__ == "__main__":
    numeros_primos = 2500

    for iteraciones in [1, 4, 10, 20]:

        for n_procesos in [
            int(os.cpu_count() / 2) or 1,
            os.cpu_count(),
            os.cpu_count() * 2,
            20,
        ]:
            start = time.time()
            res = ejecucion_multiproceso2(iteraciones, n_procesos, numeros_primos)
            logger.info(
                f"Multi_proc - {iteraciones} iter,"
                f" {n_procesos} procesos: {time.time() - start:.4}s"
            )
            logger.info(f"Resultado: {res}")