소개
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
"""
- do_thing 함수
- 2개의 argument 를 받는 함수입니다.
- 시간이 걸리는 I/O 작업을 sleep 으로 표현하였어요
- for 문을 이용한 thread 생성 및 실행
- threading.Thread 를 이용하여 스레드 객체를 생성하였어요
- target 으로 스레드로 동시 작업을 할 함수인 do_thing 을 지정 해줍니다.
- args 로 2개의 argument 에 대입해줄 값을 배열로 넘겨 줍니다.
- thread.join()
- thread.join() 은 스레드가 종료할 때까지 기다려 줍니다.
- 만약 thread.join() 이 없다면, 마지막줄 perf time 까지 출력이 먼저 일어 난답니다.
- 결과
- 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)}')
- concurrent.futures.ThreadPoolExecutor() 이용
- executor 객체를 생성합니다.
- list comprehension 이용한 do_thing 함수 5번의 병렬 수행
- executor.submit 함수를 통해서 target 함수와 인자를 전달합니다.
- list comprehension 이용하여 병렬 수행할 횟수를 지정합니다.
concurrent.futures 이 더 최근에 나왔으며, ProcessPoolExecutor() 를 사용할 시에도 비슷한 방법으로 사용이 가능하여 더 추천하지만, 각자가 구현하기 편한 방법으로 사용해도 되지 않을까 합니다.