(Python 성능 향상) concurrent.futures 동시 프로그래밍 모듈로 속도 개선해보자!
-
반응형
성능에 대해서는 프로그램을 만들고 나서나 기능 개발이 완료되고 나서나 작동이 잘되는것을 확인했는데 속도가 아쉽다면 항상 고민을 하게 됩니다.
우리는 더 나은 품질에 빠릿빠릿한 프로그램을 원하니까요!
프로그램이 원할하게 돌아가게 하기 위해선 Frontend에서의 부드러움도 중요하지만 그만큼 Backend 및 자동화를 위한 스크립트에서도 빠른 속도가 바쳐줘야 합니다.
이글은 제가 많은 시도 끝에 성능을 올려본 방법론중에 하나이며 스스로도 추후에 참고하고 싶어 기록을 남겨봅니다.
속도가 문제가 되는곳에는 그만큼 거대한 연산작업이 있다는것입니다. 그 거대한 연산작업을 기다리지 않고 병렬로 처리한다면 사용자는 우선적으로 결과값을 바로 받을 수 있어 사용성에 부드러움을 느낄 수 있습니다.
그 병렬처리를 위해 동시프로그래밍 코드가 쓰이는데 대표적으로 thread, multiprocessing.. 등이 있죠.
이번글에서는 void 타입의 메서드를 병렬로 실행시키고 바로 다음 코드로 넘어가서 실행되게하는 방법론을 공유하겠습니다.
(코드 예시)
hp회복을 빠르게 하면서 dragonBreath와 dragonArmor를 빠르게 스킬을 써야된다고 가정해봅시다.
import time
def selfHeal():
print('hp 100 회복')
time.sleep(1)
def dragonBreath():
print('용의 브레쓰')
def dragonArmor():
print('용의 갑옷')
if __name__ == "__main__":
for i in range(100):
selfHeal()
dragonBreath()
dragonArmor()
결과
저 상황에서 selfHeal(), 즉 hp 회복이 늦어지면 스킬을 쓰기도 전에 공격을 맞고 죽을 수 있습니다.
for문은 직렬로 실행되기때문에 selfHeal이 100번 다 실행될때까지 기다려야합니다.그리고 리턴값이 없기때문에 병렬로 알아서 실행되고 끝나면 바로 dragonBreath를 실행할 수 있죠.
이때 concurrent.future로 해결할 수 있습니다.!!!!
반응형
import time
import concurrent.futures
def selfHeal():
print('hp 100 회복')
time.sleep(1)
def dragonBreath():
print('용의 브레쓰')
def dragonArmor():
print('용의 갑옷')
executor = concurrent.futures.ThreadPoolExecutor(max_workers=2)
if __name__ == "__main__":
for i in range(100):
executor.submit(selfHeal)
dragonBreath()
dragonArmor()
결과
결과가 보이시나요? hp 100 회복 2번정도 실행되고 바로 용의 브레쓰와 갑옷을 사용합니다.
이렇게 사용하면 selfHeal이 병렬로 실행되어 selfHeal이 다 되기만을 기다리지 않고 용의 브레쓰와 갑옷을 바로 사용할 수 있습니
다.
저희가 프로그래밍할때 굳이 기다리지 않고 병렬로 알아서 처리되어야할 작업이 많습니다. 예를들러 api로 수정 요청을 날리거나 삭제 요청을 날리거나.. 등 굳이 결과 값을 기다리지 않고 따로 실행되고 끝내야할 작업들이 있죠. 이럴때 잘 사용하면 꼭 필요한 작업을 우선적으로 처리하고 넘어갈 수 있어서 사용자에게 필요한 값을 더 빠르게 제공해줄 수 있습니다.
그리고 사실 위에서의 concurrent.future사용법은 일반적으로 권고되지 않는 방법입니다. 왜냐하면 병렬로 메서드가 모두 실행이 다 되었다는것을 기다리지 않기 때문이죠.
위의 코드는 이래나 저래나 메서드가 모두 실행될때까지 기다리고 싶지 않을때의 사용법입니다.
다음은 concurrent.future에 대해서 일반적인 사용 방법입니다.
concurrent.futures란?
concurrent.futures는 파이썬 3.2부터 도입된 표준 라이브러리입니다. 비동기 작업을 간편하게 실행할 수 있게 해주죠. 두 가지 핵심 컴포넌트가 있습니다.
Executor: 작업을 스레드나 프로세스에 할당합니다.
Future: 작업의 결과를 나타냅니다.
두 컴포넌트를 사용하면 작업을 병렬로 실행하고, 결과를 편리하게 가져올 수 있습니다.
사용 예시
간단한 예제 코드를 살펴봅시다.
from concurrent.futures import ThreadPoolExecutor
import urllib.request
def load_url(url, timeout):
with urllib.request.urlopen(url, timeout=timeout) as conn:
return conn.read()
# URL 리스트
urls = [
'http://www.google.com',
'http://www.python.org',
'http://www.yahoo.com'
]
# 스레드 풀 생성
with ThreadPoolExecutor(max_workers=5) as executor:
# 작업 예약
future_to_url = {executor.submit(load_url, url, 60): url for url in urls}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (url, exc))
else:
print('%r page is %d bytes' % (url, len(data)))
이 코드는 ThreadPoolExecutor를 사용해 최대 5개의 스레드로 URL을 병렬로 읽어옵니다.
load_url 함수가 주어진 URL에서 데이터를 읽어옵니다.
executor.submit으로 각 URL에 대해 load_url 작업을 예약합니다.
future_to_url 딕셔너리에 Future 객체와 URL을 매핑합니다.
as_completed 함수로 작업이 완료되는 대로 결과를 출력합니다.
마치며
concurrent.futures는 멀티 스레딩과 멀티 프로세싱을 간편하게 구현할 수 있게 해줍니다. 작업을 병렬로 실행하고, 결과를 쉽게 처리할 수 있죠. 이 모듈을 활용하면 파이썬 코드의 효율성을 크게 높일 수 있습니다. 꼭 활용해보시기 바랍니다!