threading과 asyncio의 차이점 및 동시 요청 처리 방식
threading
•
OS 레벨의 스레드를 활용하여 동시 실행을 처리
•
Python의 **Global Interpreter Lock(GIL)**로 인해 CPU 바운드 작업에서는 성능이 크게 향상되지 않음
•
I/O 바운드 작업(네트워크 요청, 파일 읽기/쓰기 등)에 적합
•
컨텍스트 스위칭이 필요하므로 성능 오버헤드가 있음
•
요청이 동시에 들어오면 각 요청마다 새로운 스레드를 생성할 수 있어 중복 처리가 발생할 가능성 있음
•
threading 사용 시, Queue나 Lock을 이용해 중복 요청을 방지해야 함
asyncio
•
싱글 스레드, 싱글 프로세스 기반의 코루틴(async/await) 방식
•
GIL의 영향을 덜 받으며, 비동기 I/O 작업을 효율적으로 처리
•
이벤트 루프(Event Loop)를 이용하여 태스크(task)를 관리
•
asyncio.get_event_loop()로 실행 중인 이벤트 루프를 가져와 동일한 루프 내에서 작업을 처리할 수 있음 → 중복 요청 방지 가능
•
asyncio.ensure_future() 또는 asyncio.create_task()를 활용하면 동일한 이벤트 루프에서 여러 개의 비동기 작업을 처리 가능
threading에서 2번 요청받아 처리되는 문제 원인
•
동시 요청 처리 방식이 서로 독립적인 스레드에서 실행되기 때문
•
요청을 받을 때마다 새로운 스레드를 생성하고, 각 스레드가 동일한 요청을 처리하면서 중복 실행 발생 가능
•
예를 들어, Flask + threading 조합을 사용할 경우 동일한 API 요청이 거의 동시에 들어오면 두 개의 스레드에서 같은 로직을 실행하는 경우가 있음
asyncio.get_event_loop()를 활용한 해결 방식
•
asyncio.get_event_loop()를 이용하면 하나의 이벤트 루프에서 작업을 관리할 수 있음
•
동일한 루프 내에서 실행되므로, 특정 요청을 여러 번 실행하는 경우를 방지 가능
•
asyncio.Lock 또는 asyncio.Queue를 사용하여 중복 요청 방지 가능
threading vs asyncio 비교 정리
항목 | threading | asyncio |
실행 방식 | 멀티스레드 | 싱글 스레드 + 이벤트 루프 |
GIL 영향 | 있음 | 없음 (I/O 바운드 작업 시) |
주 사용 사례 | CPU 바운드, 동시 요청 처리 | I/O 바운드, 네트워크 요청 최적화 |
중복 요청 가능성 | 있음 (스레드가 여러 번 실행될 수 있음) | 적음 (이벤트 루프에서 관리) |
컨텍스트 스위칭 비용 | 높음 | 낮음 |
예제 코드 비교
threading 예제 (중복 요청 가능성 있음)
import threading
import time
def process_request(request_id):
print(f"Processing request {request_id}")
time.sleep(2) # Simulate work
print(f"Completed request {request_id}")
# 두 개의 스레드가 동일한 요청을 처리하는 상황 발생 가능
thread1 = threading.Thread(target=process_request, args=(1,))
thread2 = threading.Thread(target=process_request, args=(1,))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
Python
복사
출력 예시:
Processing request 1
Processing request 1
Completed request 1
Completed request 1
Plain Text
복사
동일한 요청 1이 두 번 처리됨
asyncio 예제 (이벤트 루프 활용)
import asyncio
async def process_request(request_id):
print(f"Processing request {request_id}")
await asyncio.sleep(2) # 비동기 대기 (블로킹 없음)
print(f"Completed request {request_id}")
async def main():
loop = asyncio.get_event_loop()
task1 = loop.create_task(process_request(1))
task2 = loop.create_task(process_request(2))
await asyncio.gather(task1, task2)
asyncio.run(main())
Python
복사
출력 예시:
Processing request 1
Processing request 2
Completed request 1
Completed request 2
Plain Text
복사
요청이 개별적으로 실행되지만 이벤트 루프 내에서 관리되므로 중복 실행 방지 가능
결론
•
threading은 OS 레벨에서 동작하며, 동일한 요청이 중복 실행될 가능성이 있음
•
asyncio는 이벤트 루프 기반이므로, 동일한 요청을 관리하는 방식이 다르고 중복 요청을 줄일 수 있음
•
asyncio.get_event_loop()를 활용하면, 기존의 이벤트 루프에서 요청을 처리하여 중복 실행을 막을 수 있음
•
I/O 바운드 작업(네트워크, 파일 처리 등)에서는 asyncio를 적극 활용하는 것이 성능적으로 유리함
안녕하세요
•
관련 기술 문의와 R&D 공동 연구 사업 관련 문의는 “glory@keti.re.kr”로 연락 부탁드립니다.
Hello 
•
For technical and business inquiries, please contact me at “glory@keti.re.kr”