TIL-089 | Python_Multiprocessing

15469 단어 pythonTILTIL

🌈 Python_Multiprocessing & Multithreading

🧐 지난 TIL-080 글을 통해 파이썬의 GIL(Global Interpreter Lock)에 대해 학습했었다. 처리해야하는 연산에 따라 Multiprocessing 혹은 Multithreading이 효과적으로 작용할 수 있는데 간단한 예제를 통해 파이썬에서의 적용방법을 알아보고자 한다.

GIL(Global Interpreter Lock)

GIL 정리 포스트

  • 파이썬에서는 하나의 프로세스 안에 모든 자원의 Lock을 Global하게 관리함으로써 한번에 하나의 쓰레드만 자원을 컨트롤하여 동작
  • 여러 쓰레드를 동시에 실행시키지만, 결과적으로는 GIL 때문에 한번에 하나의 쓰레드만 계산을 실행한다.
  • GIL로 Garbage collection을 쉽게 구현하게 되었지만, 멀티 코어 부분에서는 아쉬움이 있다.
  • 파이썬의 경우에는 GIL이 존재하여 멀티 쓰레드보다는 멀티 프로세스를 사용하는 것이 좋음

Multiprocessing

  • 쓰레딩 모듈로 쓰레드를 생성 할 수 있는 것과 동일한 방식으로 프로세스를 생성
  • 프로세스는 각자가 고유한 메모리 영역을 가지기 때문에 쓰레드에 비하면 메모리 사용이 늘어난다는 단점
  • 싱글 머신 아키텍처로부터 여러 머신을 사용하는 분산 애플리케이션으로 쉽게 전환 가능

파이썬의 Multiprocessing

  • 파이썬에서 멀티프로세싱을 이용하여 여러 작업을 동시에 처리할 수 있다.
#Multiprocessing 적용X
import time

start = time.time()

def count(group):
    for i in range(1, 50001):
        print(f'{group} : {i}')

num_list = ['n1', 'n2', 'n3', 'n4']

for num in num_list:
    count(num)

print(f'{time.time()-start} s')
  • 멀티프로세싱을 적용하지 않고 20만 카운트를 한 것이다.

Process

  • multiprocessing의 Process를 사용하여 Multiprocessing을 간단히 구현할 수 있다.
#multiprocessing - Process 함수 사용
start = time.time()

def count(group):
    for i in range(1, 50001):
        print(f'{group} : {i}')

if __name__ == '__main__':
    process1 = multiprocessing.Process(target=count, args=('pr1',))
    process2 = multiprocessing.Process(target=count, args=('pr2',))
    process3 = multiprocessing.Process(target=count, args=('pr3',))
    process4 = multiprocessing.Process(target=count, args=('pr4',))

    process1.start()
    process2.start()
    process3.start()
    process4.start()
    process1.join()
    process2.join()
    process3.join()
    process4.join()

    print(f'{time.time()-start} s')

  • process1 = multiprocessing.Process(target=count, args=('pr1',)) 코드에서 args=('pr1',)) 로 작성한 이유는 처음에 args=('pr1') 로 작성하였을 때 아래와 같은 에러가 났기 때문이다.

  • argument로 어떤 것들이 들어가게 된건지 확인하기 위해 count 함수를 아래와 같이 변경하여 출력해 보았다.
def count(*args):
    for i in range(1, 50001):
        print(f'{args} : {i}')

  • 'pr1' 이 하나의 문자열로 인식되지 않고 iterable하게 인식되는 것을 확인할 수 있다. 그래서 arg=('pr1',) 로 수정하였따.

Pool

  • Python에선 multiprocessing.Pool을 이용하여 멀티프로세싱을 할 수 있다.
  • Pool은 지정된 개수만큼 프로세스를 미리 만들어 놓고, 그 프로세스들 위에서 작업을 돌리는 방식이다.
  • 처음 Pool을 생성할 때에 사용될 프로세스 수를 지정하지 않는다면, os.cpu_count()의 값으로 지정된다.
  • 사용 후 처리로는 close()terminate()이 있다. close()는 더 이상 Pool에 추가 작업이 들어가지 않는다는 것을 알려주며, 지금 수행 중인 작업이 모두 끝나면 Pool의 프로세스들을 종료한다. terminate()를 사용하면, 현재 진행 중인 작업이 있더라도 즉시 Pool의 프로세스들을 종료한다. join()은 Pool의 모든 프로세스들의 종료가 완료되기를 기다린다.
  • 아래의 간단한 예제 소스코드를 보도록 하겠다.
#multiprocessing
import time
import multiprocessing 

start = time.time()

def count(group):
    for i in range(1, 50001):
        print(f'{group} : {i}')

num_list = ['n1', 'n2', 'n3', 'n4']

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=4)
    pool.map(count, num_list)
    pool.close()
    pool.join()

print(f'{time.time()-start} s')
  • processes=4로 설정하여 멀티프로세싱으로 함수를 동작시켰다.
  • 위의 예시에 비해 시간이 줄어듬을 확인하였다.
  • 처음에는 숫자의 범위를 1~10000으로 했었는데 이때는 시간 차이가 크지 않아 범위를 증가시켰더니 속도차이를 확연히 느낄 수 있었다.


🙆‍♂️ multiprocessing 모듈의 일부 함수만을 사용하여 간단한 멀티프로세싱을 작동해보았다. 상황에 따라 다양한 형태로 응용이 가능하지만 추후 점차 사용 범위를 넓혀 갈 예정이다.


📝 Reference

  1. https://wikidocs.net/85603
  2. https://niceman.tistory.com/145
  3. https://data-make.tistory.com/682

좋은 웹페이지 즐겨찾기