본문 바로가기
프로그래밍/Python

[병렬 프로그래밍] 3. Multi-Process 사용하기 with Python

by 부자 꽁냥이 2021. 5. 16.

이 포스팅은 꽁냥이가 병렬 프로그래밍 공부한 내용을 포스팅하는 곳입니다.


이번 포스팅에서는 멀티 프로세스(Multi-Process)를 사용하는 기본적인 방법에 대해서 소개하려고 한다. 이번 포스팅에서도 Corey Schafer 님의 Python Multiprocessing 강의 영상을 (아주 많이) 참고했다.

여기서 다루는 내용은 다음과 같다.

 

1. 예제

2. multiprocessing 모듈 사용하기

3. concurrent 모듈 이용하기

4. 멀티 프로세스은 언제 써야 할까?


   1. 예제

아래의 함수를 4번 실행한다고 생각해보자. 이 함수는 1초간 잠들었다가 깨어나는 동작을 수행한다.

 

def do_something():
    print('1초간 잠을 잡니다...')
    time.sleep(1)
    print('잠에서 깨었습니다...')

 

이 함수를 4번 실행해보자. 이때 실행 완료 시간은 대략 4초가 걸릴 것이다(Visual Studio Code로 실행했다).

 

import time

def do_something():
    print('1초간 잠을 잡니다...')
    time.sleep(1)
    print('잠에서 깨었습니다...')

if __name__ == '__main__':

    start = time.perf_counter()

    for _ in range(4):
        do_something()
    
    finish = time.perf_counter()

    print(f'{round(finish-start,2)}초 만에 작업이 완료되었습니다.')

 

 

역시 예상했던 대로 4초 정도 걸렸다. 위 코드를 멀티 프로세스를 이용하여 처리한다면 시간 절감 효과가 얼마나 생기는지 확인해보자.


   2. multiprocessing 모듈 사용하기

앞서 살펴보았던 코드를 multiprocessing 모듈을 이용하여 처리해보자. 아래 코드를 살펴보자.

 

import time
import multiprocessing

def do_something():
    print('1초간 잠을 잡니다...')
    time.sleep(1)
    print('잠에서 깨었습니다...')

if __name__ == '__main__':

    start = time.perf_counter()

    processes = []
    for _ in range(4):
        p = multiprocessing.Process(target=do_something) ## 각 프로세스에 작업을 등록
        p.start()
        processes.append(p)

    for process in processes:
        process.join()
    
    finish = time.perf_counter()

    print(f'{round(finish-start,2)}초 만에 작업이 완료되었습니다.')

 

line 13~17

프로세스를 담아줄 리스트를 초기화해주고(line 13) 루프를 돌면서 프로세스를 하나씩 만들어준다. 이때 target 인자를 통하여 이 프로세스가 해야 할 작업(함수)을 등록해준다(line 15). 등록해주고 난 뒤 실행할 수 있도록 start 메서드를 호출한다(line 16). 

 

line 19~20

프로세스가 끝날 때까지 다음 코드는 실행시키지 못하도록 각 프로세스마다 join을 호출한다.

 

위 코드를 실행해보자.

 

 

실행시간이 1.3초 걸렸다. 약 4배 정도 빨라졌다. 

반응형

   3. concurrent 모듈 이용하기

multiprocessing 모듈을 이용하여 멀티 프로세스를 구현하려고 하면 프로세스마다 start와 join을 해주어야 한다. 매번 그렇게 하는 것은 귀찮은 일이다. 이때 concurrent를 사용한다면 위와 같은 작업을 안 해도 된다. 아래 코드를 살펴보자.

 

import time
import concurrent.futures

def do_something():
    print('1초간 잠을 잡니다...')
    time.sleep(1)
    return '잠에서 깨었습니다...'

if __name__ == '__main__':

    start = time.perf_counter()

    with concurrent.futures.ProcessPoolExecutor() as executor:
        futures = [executor.submit(do_something) for _ in range(4)]

    for future in futures:
        print(future.result())

    finish = time.perf_counter()

    print(f'{round(finish-start,2)}초 만에 작업이 완료되었습니다.')

 

line 7

함수의 리턴 값을 이용하여 출력하기 위해 print 대신 return으로 바꾸어 주었다.

 

line 13~14

with 문을 이용하여 ProcessPoolExecutor 클래스의 인스턴스(executor)를 만들어준다. 이는 프로세스 Pool을 만들어 준 것이다. 다음으로 submit을 이용하여 프로세스 Pool에 작업을 제출한다. submit을 Future 객체를 반환하는데 여기에는 작업이 제대로 진행되었는지 제대로 진행되었다면 그 결과값을 담고 있다. 자세히 알고 싶은 분들은 여기를 참고해보자.

 

line 16~17

작업의 결과값은 Future 객체의 result 메서드를 호출하여 얻을 수 있다.

 

위 코드를 실행해보자.

 

 

1.45초 걸렸다. 기존 것보다는 빠르지만 multiprocessing을 이용한 것보다는 시간이 더 걸렸다. 왜 그런지는 잘 모르겠다 ㅠ.ㅠ...


   4. 멀티 프로세스은 언제 써야 할까?

해야 할 작업이 병렬로 작업이 가능하고 각 작업이 CPU를 많이 사용하는 작업일 때 멀티 프로세스를 사용하면 시간적인 성능을 개선할 수 있다. 하지만 Context Switch와 같은 무거운 작업들이 많이 일어날 수 있어서 오버헤드가 발생할 수 있어 오히려 성능이 더 안 좋아질 수 있다고 한다. 따라서 멀티 프로세스에 대한 지식이 많아야 멀티 프로세스를 통하여 자기가 원하는 만큼 성능을 개선할 수 있을 것이다.


아직 병렬 프로그래밍에 대한 경험이 없고 프로세스, 스레드 관련 지식도 부족하다 보니 포스팅 내용에 틀린 부분이 있을 수 있다. 알려주시면 정말 감사하겠다. 멀티 쓰레딩과 멀티 프로세스를 공부하면서 이 부분은 얕은 지식으로 접근했다가는 엄청난 사고를 일으킬 것 같은 불안감이 생겼고 '과연 잘할 수 있을까?' 하는 두려움이 생겼다. 그래도 병렬 프로그래밍 마스터가 되고 싶다...!!

 


댓글


맨 위로