소개

Python 에서 Threading 은 I/O bound 작업에 보다 더 적합하다고 앞선 포스팅에서 말씀드렸는데요.

이번엔 직접 Threading 을 구현해 보면서 사용법을 정리해볼까 합니다.

Threading 모듈을 활용한 old 한 방법을 먼저 살펴보고, python 3.2 버전부터 등장한 concurrent.futures 모듈을 활용한 좀 더 간편한 thread 사용에 대해서 정리를 해볼까 합니다.

Threading 모듈 이용(Old 방식)

threading 모듈을 이용한 old 한 방식으로 먼저 구현을 해보았습니다.

다소 시간이 걸리는 I/O 작업을 do_thing 함수에 구현하였습니다. 아래 예제에서는 sleep 을 시간이 걸리는 I/O 작업이라고 가정하였어요.

import threading
import time
import requests

# 소요 시간 확인 위한 시작 시간
start_time = time.perf_counter()

# thread 로 실행 시킬 함수
def do_thing(secs, name):
    seconds = secs
    
    print(f'{seconds} seconds sleeping {name}')
    time.sleep(seconds)
    print(f'done, {name}')

threads = []

for _ in range(5):
    th = threading.Thread(target=do_thing, args=[2, 'jang'])
    th.start()          # thread 시작
    threads.append(th)

for thread in threads:
    thread.join()       # thread 끝날 때까지 기다리기

# 소요 시간 확인 위한 끝난 시간
end_time = time.perf_counter()
# 총 소요 시간 출력
print(f'perf time: {round(end_time - start_time, 2)}')

""" 출력
2 seconds sleeping jang
2 seconds sleeping jang
2 seconds sleeping jang
2 seconds sleeping jang
2 seconds sleeping jang
done, jang
done, jang
done, jang
done, jang
done, jang
perf time: 2.0
"""

  1. do_thing 함수
    • 2개의 argument 를 받는 함수입니다.
    • 시간이 걸리는 I/O 작업을 sleep 으로 표현하였어요
  2. for 문을 이용한 thread 생성 및 실행
    • threading.Thread 를 이용하여 스레드 객체를 생성하였어요
    • target 으로 스레드로 동시 작업을 할 함수인 do_thing 을 지정 해줍니다.
    • args 로 2개의 argument 에 대입해줄 값을 배열로 넘겨 줍니다.
  3. thread.join()
    • thread.join() 은 스레드가 종료할 때까지 기다려 줍니다.
    • 만약 thread.join() 이 없다면, 마지막줄 perf time 까지 출력이 먼저 일어 난답니다.
  4. 결과
    • 2초식 sleep 하는 함수를 5번 실행하였지만, thread 로 병렬 처리가 되어 소요시간은 2초만 걸린 것으로 확인이 됩니다.

Concurrent.futures 이용(New 방식)

python 3.2 버전 이상부터 concurrent.futures 모듈이 나오면서 threading 모듈보다 더 간편히 스레드를 이용할 수 있게 되었습니다.

위와 동일한 코드를 concurrent.futures 로 구현한 코드를 보면 더 직관적임을 느낄 수 있으실 거에요.

concurrent.futures 는 multiprocessing 에서도 사용할 수 있는데, 사용 방법이 거의 동일하여 양쪽 모두 적절한 환경에서 더욱 간편하게 사용하실 수 있을 것 같아요.

import concurrent.futures
import time
import requests

# 소요 시간 확인 위한 시작 시간
start_time = time.perf_counter()

# thread 로 실행 시킬 함수
def do_thing(secs, name):
    seconds = secs
    
    print(f'{seconds} seconds sleeping {name}')
    time.sleep(seconds)
    print(f'done, {name}')
    
# concurrent.futures.ThreadPoolExecutor() 이용한 thread executor 생성
with concurrent.futures.ThreadPoolExecutor() as executor:
    # list comprehension 을 통한 병렬 실행
    results = [executor.submit(do_thing, 2, 'jang') for _ in range(5)]
    
# 소요 시간 확인 위한 끝난 시간
end_time = time.perf_counter()
# 총 소요 시간 출력
print(f'perf time: {round(end_time - start_time, 2)}')

  1. concurrent.futures.ThreadPoolExecutor() 이용
    • executor 객체를 생성합니다.
  2. list comprehension 이용한 do_thing 함수 5번의 병렬 수행
    • executor.submit 함수를 통해서 target 함수와 인자를 전달합니다.
    • list comprehension 이용하여 병렬 수행할 횟수를 지정합니다.

concurrent.futures 이 더 최근에 나왔으며, ProcessPoolExecutor() 를 사용할 시에도 비슷한 방법으로 사용이 가능하여 더 추천하지만, 각자가 구현하기 편한 방법으로 사용해도 되지 않을까 합니다.

More Reading

참고

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

Back To Top