TIL

[Python] Thread 설계 의도를 파악한 성능 개선. 7초에서 3초로

친환경사과 2023. 4. 11. 14:42

 

💻 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 공식 문서

threading 공식 문서

context manager 공식 문서

ThreadPoolExecutor_vs_Thread


🍎 PR에서 변경 사항을 확인할 수 있습니다.

🍎 Github 주소 :친환경 사과

 

EcoFriendlyAppleSu - Overview

EcoFriendlyAppleSu has 24 repositories available. Follow their code on GitHub.

github.com