ONE QUESTION AT A TIME.

천천히 생각하고, 분명하게.

TODAY’S QUESTION1146 / 1500
언어·런타임난이도 하5분 답변 목표 · 보강본

asyncio 요청 처리 중 동기 HTTP 호출이나 긴 계산을 넣었더니 다른 요청까지 멈춥니다. 이벤트 루프에서 무엇이 막히며 어떤 실행 방식으로 분리하나요?

잠깐, 답변을 보기 전에
먼저 내 말로 설명해 볼까요?

답변과 점검 포인트 펼치기답변 접기
01

구두 답변

정의를 외우기보다, 원리와 조건을 연결해 설명해 보세요.

async 함수는 함수 내부의 모든 작업을 자동으로 논블로킹으로 바꾸지 않습니다. 일반적인 asyncio 서버에서는 이벤트 루프가 코루틴을 번갈아 실행하며, 코루틴이 await에서 제어를 넘기거나 실행을 마칠 때까지 같은 스레드에서 계속 실행될 수 있습니다. 따라서 동기 HTTP 호출, 동기 파일 I/O, 긴 for 계산이 이벤트 루프를 붙잡으면 그동안 다른 요청도 실행되지 않습니다.

이벤트 루프를 막는 동기 구간

아래는 time과 fetch를 정의한 설명용 예제입니다. 마지막 줄에서 코루틴을 실제로 실행합니다. async로 선언했어도 동기 time.sleep이 이벤트 루프를 2초 동안 멈춥니다. 반면 await asyncio.sleep(2)는 기다리는 동안 다른 코루틴에 실행 기회를 줍니다.

import asyncio
import time

async def fetch():
    return "ok"

async def handler():
    time.sleep(2)          # 이벤트 루프를 막음
    return await fetch()

print(asyncio.run(handler()))  # 약 2초 뒤 ok 출력

저는 먼저 이벤트 루프 지연, 해당 호출 시간, 작업 풀 대기를 분리해 측정하겠습니다. I/O 라이브러리를 비동기 클라이언트로 바꾸는 것이 가장 직접적인 해결책입니다. 바꿀 수 없는 블로킹 호출은 제한된 스레드 풀에 보내고, GIL이 활성화된 일반 CPython 빌드에서는 순수 Python CPU 계산을 스레드로 옮겨도 코어 병렬 실행을 보장하지 않으므로 프로세스 풀이나 별도 작업 서비스와 비교합니다.

격리해도 남는 실행

격리했다고 끝나지는 않습니다. 스레드 풀에 보낸 동기 함수는 대기 중인 코루틴이 취소돼도 이미 실행 중이면 계속될 수 있으므로 호출 타임아웃, 풀 크기, 큐 상한, 외부 작업의 자체 취소를 함께 설계해야 합니다. CPU 작업을 프로세스로 옮길 때는 직렬화와 데이터 복사 비용도 포함합니다. 최종 검증은 정상 평균이 아니라 이벤트 루프 지연 p95·p99와 작업 풀 포화, 취소 뒤 남은 작업을 함께 보는 방식으로 하겠습니다.

선택 기준과 검증

to_thread에 제출한 작업은 코루틴이 취소돼도 실행을 계속할 수 있습니다. semaphore를 async with로 감싸면 허가가 먼저 풀려 논리적 동시성 상한보다 실제 스레드 작업 수가 커질 수 있으므로, 실행 완료에 허가를 묶거나 제한된 executor·제출 큐 상한을 사용하겠습니다. Python 3.11+에서 loop lag·executor 대기·실제 완료 잔여를 각각 관찰하겠습니다.

이미 완료된 awaitable이나 중단 없이 반환하는 async 함수를 await한다고 매번 다른 작업에 실행 기회를 주는 것은 아닙니다. 실제로 중단되는 지점이 필요하므로 긴 반복문이 빠른 async 호출만 반복해도 이벤트 루프를 오래 점유할 수 있습니다. 작은 단위로 나누어 협력적으로 양보할지, CPU 작업을 프로세스 풀로 넘길지 계산 비용과 응답 예산으로 결정하겠습니다.

asyncio.to_thread는 호출 시 코루틴을 반환하며 실제 제출 시점은 그 코루틴이 실행되는 때와 연결됩니다. run_in_executor처럼 호출 즉시 제출하는 API와도 구분해야 합니다. 취소된 요청이 허가를 조기에 반환하지 않게 실제 concurrent future 완료에 자원 수명을 묶고, 취소 불가능한 함수의 개수는 제한된 executor로 통제하겠습니다. timeout 후 성공한 외부 변경은 결과를 단순 폐기해도 되지 않을 수 있으므로, 변경 요청에는 멱등 키와 결과 대사를 별도로 둡니다.

02

득점 포인트

이해가 드러나는 지점을 확인해 보세요.

  • async 선언과 실제 제어권 양도를 구분한다.
  • 동기 I/O·CPU·비동기 I/O의 격리 수단을 선택한다.
  • 이벤트 루프 지연·풀 대기·취소 잔여를 따로 측정한다.
03

감점 포인트

놓치기 쉬운 조건과 오해를 짚어 보세요.

  • async 선언이면 동기 호출도 자동으로 다른 스레드에서 실행된다고 말한다.
  • to_thread 취소가 이미 실행 중인 함수까지 즉시 중단한다고 설명한다.
  • CPU 계산은 스레드 수만 늘리면 활성 GIL CPython에서 병렬화된다고 단정한다.
04

더 파고들 거리

하나 더 질문하며 이해의 범위를 넓혀 보세요.

득점·감점 포인트는 학습용 자기 점검 기준이며, 특정 회사의 실제 채점표가 아닙니다.

원리부터 공부하고 싶다면

답변을 보충하는 개념·예제·슈도코드를 읽어 보세요.

← 전체 질문 보기