프로그래밍/C, C++, Java, Python

Python eager task factory 기준

포도알77 2026. 9. 18. 07:50

본문 중간의 쿠팡 추천 상품 구매시 쿠팡 파트너스에서 일정액의 수수료를 제공받습니다.

Python asyncio.eager_task_factory는 task를 만들 때 coroutine을 이벤트 루프에 나중에 맡기지 않고 즉시 실행해 보는 task factory입니다. coroutine이 바로 끝나면 이벤트 루프에 예약되지 않고 완료될 수 있고, 실행 중 await로 막히면 그때 이벤트 루프에 예약됩니다.

선택 기준은 단순합니다. 캐시 조회처럼 실제 I/O 없이 바로 끝나는 coroutine이 많고, task 실행 순서 변화가 문제가 되지 않는 코드에서만 검토할 만합니다. 반대로 task 생성 뒤 실행이 “나중에” 시작된다는 전제를 둔 코드라면 기본 동작을 유지하는 편이 안전합니다.

eager task factory는 무엇을 바꾸나

일반적인 asyncio.create_task()는 coroutine을 task로 감싸고 실행을 이벤트 루프에 예약합니다. 그래서 현재 coroutine이 제어권을 넘긴 뒤 task가 실행되는 흐름으로 이해하기 쉽습니다.

asyncio.eager_task_factory를 이벤트 루프 task factory로 설정하면 다르게 움직입니다. task 생성 과정에서 coroutine 본문이 동기적으로 먼저 실행됩니다. coroutine이 결과를 반환하거나 예외를 던지면 task는 이벤트 루프에 예약되지 않고 끝날 수 있습니다. coroutine이 block되면, 즉 실제로 기다릴 awaitable을 만나면 그때 이벤트 루프에 예약됩니다.

import asyncio

async def cached_lookup(key):
    if key == "hit":
        return "cached"
    await asyncio.sleep(0.1)
    return "loaded"

async def main():
    loop = asyncio.get_running_loop()
    loop.set_task_factory(asyncio.eager_task_factory)

    task = asyncio.create_task(cached_lookup("hit"))
    print(task.done(), task.result())

asyncio.run(main())

위 예시처럼 동기적으로 끝나는 coroutine은 task 생성 직후 이미 완료될 수 있습니다. 이 점이 성능상 이점의 출발점이지만, 동시에 가장 중요한 주의점이기도 합니다. 실행 시점이 바뀌기 때문입니다.

언제 이점이 생기나

공식 문서는 캐시나 memoization 때문에 실제 I/O 없이 동기적으로 끝나는 coroutine을 대표적인 사례로 듭니다. 이런 coroutine은 이벤트 루프에 예약했다가 다시 실행하는 비용을 줄일 수 있습니다. Python 3.12 변경 설명도 일부 사용 사례에서 속도 개선이 가능하다고 설명합니다.

다만 “async 함수니까 전부 빨라진다”는 뜻은 아닙니다. 네트워크 요청, 파일 I/O, 타이머 대기처럼 대부분의 호출이 곧바로 await에서 멈추는 코드라면 eager 실행의 이점은 제한적입니다. 병목이 외부 I/O라면 task 생성 방식보다 I/O 대기 시간이 더 큰 영향을 줍니다.

상황 검토 기준 주의점
캐시 hit가 많은 async 조회 동기 완료 비율이 높으면 후보 실행 순서 테스트가 필요함
항상 외부 I/O를 기다리는 작업 효과가 작을 가능성이 큼 I/O 병목을 먼저 봐야 함
task 생성 순서와 side effect가 얽힌 코드 기본 동작 유지가 무난함 즉시 실행으로 순서가 바뀔 수 있음
라이브러리 내부 task factory 호출자 이벤트 루프 정책과 충돌 여부 확인 전역적인 영향 범위를 줄여야 함

 

 

실행 순서 변화가 왜 중요한가

eager task 실행은 의미가 바뀌는 옵션입니다. 공식 문서는 coroutine이 즉시 실행되므로 task 실행 순서가 달라질 수 있다고 경고합니다. 이는 디버깅 로그 순서, shared state 변경, callback 등록 시점, 테스트에서 기대하는 이벤트 루프 tick 수에 영향을 줄 수 있습니다.

async def work(events):
    events.append("work-start")
    await asyncio.sleep(0)
    events.append("work-end")

async def main():
    events = []
    task = asyncio.create_task(work(events))
    events.append("after-create")
    await task
    print(events)

일반 task factory에서는 after-create가 먼저 기록될 수 있는 구조입니다. eager task factory에서는 create_task() 호출 중 work-start가 먼저 실행될 수 있습니다. 코드가 이런 순서에 의존한다면 eager 실행은 성능 옵션이 아니라 동작 변경으로 봐야 합니다.

Python 3.14의 eager_start 인자

Python 3.14 문서 기준으로 asyncio.create_task()TaskGroup.create_task()에는 eager_start keyword-only 인자가 있습니다. 이 인자는 task를 만들 때 eager 방식으로 시작할지 지정하는 값입니다. 인자를 넘기지 않으면 이벤트 루프의 task factory 설정을 따릅니다.

이 점은 전역 task factory를 바꾸지 않고 특정 task에서만 의도를 드러내고 싶을 때 중요합니다. 다만 eager_start가 실제로 어떻게 적용되는지는 현재 이벤트 루프와 task factory가 해당 인자를 처리하는 방식과 연결됩니다. Python 3.14 문서에서는 task factory가 (loop, coro, **kwargs) 형태를 맞추고 모든 keyword argument를 전달해야 한다고 설명합니다.

async def main():
    task = asyncio.create_task(cached_lookup("hit"), eager_start=True)
    result = await task
    print(result)

TaskGroup과 함께 볼 때

TaskGroup.create_task()도 Python 3.14 문서에서 eager_start를 받는 signature를 갖습니다. TaskGroup은 관련 task를 묶고 블록을 빠져나갈 때 모두 기다리는 구조적 동시성 API입니다. eager 실행은 task의 시작 시점을 바꾸는 옵션이고, TaskGroup은 task들의 생명주기를 묶는 도구입니다.

두 기능을 함께 사용할 때도 기준은 같습니다. 그룹 안에서 생성한 task가 즉시 일부 코드를 실행해도 괜찮은지, 첫 await 전 side effect가 순서 문제를 만들지 않는지 확인해야 합니다. 실패 처리나 취소 기준은 TaskGroup의 규칙을 따르지만, task 생성 시점에 coroutine 코드가 이미 실행될 수 있다는 점은 별도로 봐야 합니다.

적용 전 점검할 것

첫째, 동기 완료가 실제로 흔한지 측정해야 합니다. 캐시 hit가 드물면 eager 실행으로 얻는 이점은 작습니다. 둘째, task 생성 직후 실행 순서가 바뀌어도 안전한지 테스트해야 합니다. 특히 로그, metric, callback, shared collection 갱신처럼 눈에 덜 띄는 side effect를 확인해야 합니다.

셋째, 적용 범위를 작게 잡는 편이 좋습니다. 애플리케이션 전체 이벤트 루프에 task factory를 설정하면 예상보다 많은 코드가 영향을 받을 수 있습니다. 특정 호출 지점에서 eager_start를 검토하거나, 이벤트 루프를 직접 관리하는 좁은 영역에서만 설정하는 방식이 더 검증하기 쉽습니다.

기본 선택 기준

대부분의 코드에서는 기본 task 실행 방식이 더 예측하기 쉽습니다. asyncio.eager_task_factory는 “더 빠른 기본값”이 아니라 “즉시 완료 가능성이 높은 coroutine에 한해 실행 순서 변경을 받아들이는 선택지”로 보는 편이 맞습니다.

따라서 캐시 hit 중심의 짧은 coroutine, 충분한 테스트, 제한된 적용 범위가 함께 있을 때만 검토합니다. 조건이 애매하면 기본 task factory를 유지하고, 병목을 먼저 측정하는 것이 안전한 기준입니다.

FAQ

eager task factory를 쓰면 coroutine이 항상 동기적으로 끝나나?

아닙니다. coroutine이 block 없이 반환하거나 예외를 던질 때만 이벤트 루프 예약 없이 끝날 수 있습니다. 중간에 기다릴 awaitable을 만나면 task는 이벤트 루프에 예약됩니다.

성능 개선을 기대하고 바로 켜도 되나?

권장하기 어렵습니다. 공식 문서도 즉시 실행이 의미 변화라고 설명합니다. 캐시 hit 비율과 실행 순서 영향이 확인된 좁은 코드부터 보는 편이 안전합니다.

Python 3.11에서도 사용할 수 있나?

표준 라이브러리의 asyncio.eager_task_factoryasyncio.create_eager_task_factory는 Python 3.12에 추가되었습니다. Python 3.11 런타임에서는 이 API를 전제로 코드를 작성할 수 없습니다.

반응형

'프로그래밍 > C, C++, Java, Python' 카테고리의 다른 글

Python TypeIs 기준  (1) 2026.09.19
Python TaskGroup 기준  (0) 2026.09.18
Python asyncio.Runner 기준  (0) 2026.09.16
Python Thread context 기준  (0) 2026.09.15
JavaScript Temporal 기준  (0) 2026.09.14
페이스북으로 공유카카오톡으로 공유카카오스토리로 공유트위터로 공유URL 복사