Python

[Python] billiard - 멀티프로세싱

비번변경 2023. 5. 27. 23:54

개요

이전에 2023.02.19 - [Python] multiprocessing을 통해 Python으로 멀티프로세싱을 수행해 보았는데, 최근 billiard라는 다른 패키지를 사용하여 멀티프로세싱을 수행하게 되었다. 이 글에서는 billiard 패키지 사용법을 정리해 둔다.

 

 

billiard

Python 2.7 multiprocessing 패키지를 Fork한 패키지다. Celery에서 사용하는 패키지로 Celery 팀에 의해 유지보수되고 있다.

Github : https://github.com/celery/billiard

 

+ Airflow에서 CeleryExecutor를 사용하고 있다면 추가 패키지 설치 없이 멀티프로세싱을 적용할 수 있을 것 같다.

 

 

설치

pip를 이용해 설치할 수 있다.

pip install billiard

 

 

모듈 임포트

import는 다음과 같이 한다.

import billiard
import billiard as mp

 

 

현재 프로세스 정보 확인

current_process 함수를 사용하여 실행된 프로세스의 정보가 저장된 객체를 얻을 수 있다. 

import billiard as mp

if __name__ == "__main__":
    proc = mp.current_process()
    print(proc.name)
    print(proc.pid)

 

 

서버 cpu 개수 확인

cpu_count 함수를 이용해 실행 서버 또는 컴퓨터의 CPU 개수를 확인할 수 있다. 프로세스 pool 사용 시 모든 CPU를 사용하면 OS 동작에 필요한 프로세스가 제때 처리되지 않아 불안정해질 수 있다. 따라서 적절하게 CPU 개수를 조절해야 한다. 2개 정도는 여유 있게 남겨두는 것이 좋다.

import billiard as mp

if __name__ == "__main__":
    print(mp.cpu_count())

 

 

프로세스 pool 사용

여러 개의 자식 프로세스를 실행시킬 때는 Pool 객체를 이용해 실행시킬 프로세스의 개수를 정의한 뒤, map 함수를 사용해 자식 프로세스가 실행할 함수와 함수 실행에 필요한 데이터를 전달한다.

import billiard as mp


def worker(cnt):
    print(cnt)


if __name__ == "__main__":
    list_data = [f'cnt_{i}' for i in range(200)]
    pool = mp.Pool(processes=3)
    pool.map(worker, list_data)

 

Pool은 with문을 통해 사용해도 된다.

import billiard as mp


def square(x):
    return x ** 2


if __name__ == '__main__':
    # Determine the number of worker processes based on CPU count
    cnt_cpu = mp.cpu_count() - 2
    numbers = [1, 2, 3, 4, 5]

    list_result = list()
    with mp.Pool(cnt_cpu) as pool:
        for result in pool.imap(square, numbers):
            list_result.append(result)
    print(list_result)

 

 

map VS imap

눈썰미가 좋은 사람이라면 예시 코드에 map 함수와 imap 함수를 사용한 것을 발견할 수 있을 것이다. 두 함수는 프로세스 pool에 실행할 함수와 데이터를 전달하는 함수이다. 하지만 동작 방식에 차이가 있다.

billiard 패키지보다 multiprocessing 패키지를 기준으로 정리된 글이 많은데, 크게 차이는 없을 것 같다. 관련 내용을 적어둔다.

 

map

iterable 데이터를 리스트로 변환하고, 풀 내 프로세스에 지정한 cpu 수(프로세스 수)만큼 데이터를 한 번에 전달한다. 한 번에 한 데이터를 전달하는 것보다 성능이 좋지만, 전달한 데이터를 메모리에 보관해야 하므로 메모리 사용량에 주의해야 한다.

 

imap

iterable 데이터를 리스트로 변환하지 않으며, 각 요소를 반복적으로 전달한다. 그리고 결과가 모두 완료될 때까지 기다리지 않고 바로 전달받을 수 있다. 

 

 

 

 

참고 문서

https://pypi.org/project/billiard/

https://stackoverflow.com/questions/26520781/multiprocessing-pool-whats-the-difference-between-map-async-and-imap

https://superfastpython.com/multiprocessing-pool-imap/

https://velog.io/@finallazy/Python-multiprocessing-Pool