[Python] Thread 설계 의도를 파악한 성능 개선. 7초에서 3초로
💻 Crawling Application Code에서 I/O Bound 작업 처리 시, threading Module과 concurrent.futures Module를 사용했습니다.
- 두 Module의 장단점을 알지 못한 채 사용해 이번 포스팅에서 깊게 다뤄보겠습니다.
🍎threading Module과 concurrent.futures Module을 사용하는 이유는 해당 포스팅에서 확인할 수 있습니다.
🍎 thread.Thread와 concurrent.futures.ThreadPoolExecutor의 특징을 알아봅시다.
🍏thread.Thread의 특징
- Process안에 존재해 작업 처리 시 사용됩니다. Thread를 사용하는 두 가지 이유는 아래와 같습니다.
1️⃣ Target Method를 실행할 수 있습니다.
# a target function that does something
def work()
# do something...
# create a thread to execute the work() function
thread = Thread(target=work)
# start the thread
thread.start()
- Target Method는 전달된 Parameter 이외의 외부 상태와 상호 작용하지 않고 값을 반환하지 않는 일회성 작업을 실행하는 데 유용합니다.
2️⃣ 클래스 확장과 thread.run()을 확장할 수 있습니다.
# define a custom thread
class CustomThread(Thread):
# custom run function
def run():
# do something...
# create the custom thread
thread = CustomThread()
# start the thread
thread.start()
- Thread를 재정의 하거나 확장한다면 대상 함수를 호출하는 것보다 더 많은 유연성을 얻을 수 있습니다.
- Thread 클래스를 확장하는 것은 수명이 긴 작업과 Application 내의 서비스에 적합합니다.
🍏concurrent.futures.ThreadPoolExecutor의 특징
- Max_workers를 통해 Thread의 수를 지정해 Thread Pool을 만들 수 있습니다.
executor = ThreadPoolExecutor(max_workers=10)
- 또한, 제공하는 map()을 통해 iterable Data를 처리할 수 있습니다. target_method(아래 코드에선 task)에 iterable Data가 인자 값으로 들어갈 때, Pool에 존재하는 Thread가 각 item을 처리합니다.
for result in executor.map(task, items):
# process result...
🍎 thread.Thread와 concurrent.futures.ThreadPoolExecutor의 유사점과 차이점을 알아봅시다.
🍏 유사점
1️⃣ 모두 python Thread를 사용합니다.
2️⃣ 모두 특정 상황에 대응하여 단기적으로 수행하는 일시적인 작업에 적합합니다.(Ad hos task)
- 시스템 전반에 거친 작업이 아닌 일시적인 작업이 필요할 때 사용하면 좋습니다.
3️⃣ 모두 python GIL의 적용을 받습니다.
- 따라서, 모두 I/O Bound 작업을 처리하는데 적합합니다.
🍏 차이점
1️⃣ 이기종 작업과 동종 작업
- Thread는 동종 작업을 실행하도록 설계되었습니다. 확장된 Thread의 단일 작업 유형만 지원합니다.
- ThreadPoolExecutor는 이기종 작업, 즉 서로 유사하지 않은 작업을 실행하도록 설계되었습니다. 예를 들어, Thread Pool에 다루는 작업이 각각 다른 대상의 함수일 수 있습니다.
2️⃣ 재사용과 일회용
- Thread는 일회용으로 설계되었습니다. 확장과 관계없이 작업 실행 후 새 인스턴스를 만들어야 합니다. (Java
Thread run() 역시 마찬가지입니다.)
- ThreadPoolExecutor는 Pool이 동작할 동안 Thread는 활성 상태로 유지되며 Pool이 종료될 때까지 작업을 실행할 수 있습니다.
3️⃣ 여러 작업 대 단일 작업
- Thread는 Parameter 또는 클래스의 확장하여 단일 작업을 실행하도록 설계되었습니다. 동시 작업을 관리하기 위한 기본 제공 도구는 존재하지 않습니다.
- ThreadPoolExecutor는 여러 작업을 실행하도록 설계되었습니다. 예를 들어 map()의 경우 여러 함수 호출을 동시에 수행할 수 있습니다.
🍎 Python Applicaion Code Refactoring
- 아래 코드는 JBLY 프로젝트 일부입니다.
🛠️ Refactoring 전 : threading module 사용
- Thread를 설계의 의도에 어긋나게 사용하고 있다는 것을 알 수 있습니다.
- 이기종의 작업을 처리하고 있을뿐더러 일회용으로 사용되는 모습을 볼 수 있습니다.
🛠️ Refactoring 후 : concurrent.futures module 사용
- Thread Pool에 있는 Thread를 재사용하며 서로 다른 작업을 처리할 수 있게 변경했습니다.!
✅ 7초 걸린 URL Parsing 작업을 3초대로 줄일 수 있었습니다.
- Thread 재사용을 하지 않고 작업을 진행했을 때 이미지입니다.
- Thread Pool을 통해 Thread를 재사용하며 작업을 처리했을 때 이미지입니다.
✅ 어떤 기술을 프로젝트에 적용할 때, Publisher의 설계 의도를 알고 사용하는 것이 중요하다는 것을 깨달았습니다.
📚 참고 사이트
concurrent.futures Module 공식 문서
🍎 PR에서 변경 사항을 확인할 수 있습니다.
🍎 Github 주소 :친환경 사과