비동기 Python: 뭐가 필요항가요? 🐍🐍🐍
Takashi Yamamoto
Infrastructure Engineer · Leapcell

Python 코루틴 개발 프로세스 및 구형과 신형 코루틴 심층 분석
1. Python 코루틴의 역사적 진화
Python의 오랜 개발 과정에서 코루틴 구현은 몇 가지 중요한 변화를 겪었습니다. 이러한 변화를 이해하면 Python 비동기 프로그래밍의 본질을 더 잘 파악하는 데 도움이 됩니다.
1.1 초기 탐색 및 기본 기능 도입
- Python 2.5: 이 버전에서는
.send()
,.throw()
,.close()
메서드가 제너레이터에 도입되었습니다. 이러한 메서드의 등장으로 제너레이터는 단순한 반복자 그 이상이 되었습니다. 그들은 코루틴 개발을 위한 특정 기반을 마련하면서 더욱 복잡한 상호 작용 기능을 갖기 시작했습니다. 예를 들어,.send()
메서드는 제너레이터에 데이터를 전송할 수 있으며, 이는 제너레이터가 단방향으로만 출력할 수 있다는 이전 제한을 깨뜨립니다. - Python 3.3:
yield from
구문이 도입되었는데, 이는 중요한 이정표였습니다. 이를 통해 제너레이터는 반환 값을 수신할 수 있었고 코루틴은yield from
을 사용하여 직접 정의할 수 있었습니다. 이 기능은 코루틴 작성을 단순화하고 코드의 가독성과 유지 관리성을 향상시켰습니다. 예를 들어,yield from
을 사용하면 복잡한 제너레이터 작업을 다른 제너레이터에 편리하게 위임하여 코드 재사용 및 논리적 분리를 달성할 수 있습니다.
1.2 표준 라이브러리 지원 및 구문 개선
- Python 3.4:
asyncio
모듈이 추가되었는데, 이는 Python이 최신 비동기 프로그래밍으로 나아가는 데 중요한 단계였습니다.asyncio
는 이벤트 루프 기반 비동기 프로그래밍 프레임워크를 제공하여 개발자가 매우 효율적인 비동기 코드를 보다 편리하게 작성할 수 있도록 합니다. 이벤트 루프 및 작업 관리와 같은 핵심 기능을 포함하여 코루틴 실행을 위한 강력한 인프라를 제공합니다. - Python 3.5:
async
및await
키워드가 추가되어 구문 수준에서 비동기 프로그래밍에 대한 보다 직접적이고 명확한 지원을 제공합니다.async
키워드는 비동기 함수를 정의하는 데 사용되며, 함수가 코루틴임을 나타냅니다.await
키워드는 코루틴의 실행을 일시 중지하고 비동기 작업이 완료될 때까지 기다리는 데 사용됩니다. 이 구문 설탕은 비동기 코드의 작성 스타일을 동기 코드의 작성 스타일과 더 가깝게 만들어 비동기 프로그래밍의 임계값을 크게 줄입니다.
1.3 성숙 및 최적화 단계
- Python 3.7:
async def + await
를 사용하여 코루틴을 정의하는 방법이 공식적으로 확립되었습니다. 이 방법은 더 간결하고 간단하며 Python에서 코루틴을 정의하는 표준 방법이 되었습니다. 이는 비동기 프로그래밍의 구문 구조를 더욱 강화하여 개발자가 비동기 코드를 보다 자연스럽게 작성할 수 있도록 합니다. - Python 3.10:
yield from
을 사용하여 코루틴을 정의하는 방법이 제거되어 Python 코루틴 개발의 새로운 단계를 나타냅니다. 이는async
및await
를 기반으로 하는 새로운 코루틴 시스템에 더욱 초점을 맞추고 다양한 구현 방법으로 인해 발생하는 혼란을 줄입니다.
1.4 구형 및 신형 코루틴의 개념 및 영향
구형 코루틴은 yield
및 yield from
과 같은 제너레이터 구문을 기반으로 구현됩니다. 반면에 새로운 코루틴은 asyncio
, async
및 await
와 같은 키워드를 기반으로 합니다. 코루틴 개발 역사상 두 가지 구현 방법에는 교차 기간이 있었습니다. 그러나 구형 코루틴의 제너레이터 기반 구문은 제너레이터와 코루틴의 개념을 혼동하기 쉽게 만들어 학습자에게 어려움을 야기했습니다. 따라서 코루틴의 두 가지 구현 방법 간의 차이점을 깊이 이해하는 것은 Python 비동기 프로그래밍을 마스터하는 데 매우 중요합니다.
2. 구형 코루틴 검토
2.1 핵심 메커니즘: yield
키워드의 마법
구형 코루틴의 핵심은 yield
키워드에 있으며, 이 키워드는 코드 실행 일시 중지 및 재개, 함수 간 교대로 실행, CPU 리소스 전송 등 강력한 기능을 함수에 부여합니다.
2.2 코드 예제 분석
import time def consume(): r = '' while True: n = yield r print(f'[consumer] Starting to consume {n}...') time.sleep(1) r = f'{n} consumed' def produce(c): next(c) n = 0 while n < 5: n = n + 1 print(f'[producer] Produced {n}...') r = c.send(n) print(f'[producer] Consumer return: {r}') c.close() if __name__ == '__main__': c = consume() produce(c)
이 예제에서 consume
함수는 소비자 코루틴이고 produce
함수는 생산자 함수입니다. consume
함수의 while True
루프를 통해 계속 실행할 수 있습니다. n = yield r
줄이 중요합니다. 실행이 이 줄에 도달하면 consume
함수의 실행 흐름이 일시 중지되고 r
값이 호출자(즉, produce
함수)로 반환되고 함수의 현재 상태가 저장됩니다.
produce
함수는 next(c)
를 통해 consume
코루틴을 시작한 다음 자체 while
루프에 들어갑니다. 각 루프에서 데이터 조각(n
)을 생성하고 c.send(n)
을 통해 데이터를 consume
코루틴으로 보냅니다. c.send(n)
은 데이터를 consume
코루틴으로 보낼 뿐만 아니라 consume
코루틴의 실행을 재개하여 n = yield r
줄부터 계속 실행되도록 합니다.
2.3 실행 결과 및 분석
실행 결과:
[producer] Produced 1...
[consumer] Starting to consume 1...
[producer] Consumer return: 1 consumed
[producer] Produced 2...
[consumer] Starting to consume 2...
[producer] Consumer return: 2 consumed
[producer] Produced 3...
[consumer] Starting to consume 3...
[producer] Consumer return: 3 consumed
[producer] Produced 4...
[consumer] Starting to consume 4...
[producer] Consumer return: 4 consumed
[producer] Produced 5...
[consumer] Starting to consume 5...
[producer] Consumer return: 5 consumed
소비자 consume
이 n = yield r
까지 실행되면 프로세스가 일시 중지되고 CPU가 호출자 produce
로 반환됩니다. 이 예제에서 consume
및 produce
의 while
루프는 함께 작동하여 간단한 이벤트 루프 기능을 시뮬레이션합니다. 그리고 yield
및 send
메서드는 작업의 일시 중지 및 재개를 구현합니다.
구형 코루틴 구현을 요약하면 다음과 같습니다.
- 이벤트 루프:
while
루프 코드를 수동으로 작성하여 구현됩니다. 이 방법은 비교적 기본적이지만 개발자에게 이벤트 루프의 원칙에 대한 더 깊은 이해를 제공합니다. - 코드 일시 중지 및 재개:
yield
제너레이터의 특성을 사용하여 달성됩니다.yield
는 함수 실행을 일시 중지할 뿐만 아니라 함수 상태를 저장하여 재개될 때 일시 중지된 위치에서 함수가 계속될 수 있도록 합니다.
3. 신형 코루틴 검토
3.1 이벤트 루프를 기반으로 하는 강력한 시스템
새로운 코루틴은 asyncio
, async
및 await
와 같은 키워드를 기반으로 이벤트 루프 메커니즘을 핵심으로 구현됩니다. 이 메커니즘은 이벤트 루프, 작업 관리 및 콜백 메커니즘을 포함하여 보다 강력하고 효율적인 비동기 프로그래밍 기능을 제공합니다.
3.2 주요 구성 요소 기능 분석
- asyncio: 새로운 코루틴 실행의 기초가 되는 이벤트 루프를 제공합니다. 이벤트 루프는 모든 비동기 작업을 관리하고, 실행을 예약하고, 각 작업이 적절한 시간에 실행되도록 하는 역할을 합니다.
- async: 함수를 코루틴 함수로 표시하는 데 사용됩니다. 함수가
async def
로 정의되면 코루틴이 되고await
키워드를 사용하여 비동기 작업을 처리할 수 있습니다. - await: 프로세스를 일시 중단하는 기능을 제공합니다. 코루틴 함수에서
await
가 실행되면 현재 코루틴의 실행이 일시 중지되고await
다음에 오는 비동기 작업이 완료될 때까지 기다린 다음 실행이 재개됩니다.
3.3 코드 예제에 대한 자세한 설명
import asyncio async def coro1(): print("start coro1") await asyncio.sleep(2) print("end coro1") async def coro2(): print("start coro2") await asyncio.sleep(1) print("end coro2") # 이벤트 루프 생성 loop = asyncio.get_event_loop() # 작업 생성 task1 = loop.create_task(coro1()) task2 = loop.create_task(coro2()) # 코루틴 실행 loop.run_until_complete(asyncio.gather(task1, task2)) # 이벤트 루프 닫기 loop.close()
이 예제에서는 두 개의 코루틴 함수 coro1
과 coro2
가 정의됩니다. "start coro1"을 인쇄한 후 coro1
함수는 await asyncio.sleep(2)
를 통해 2초 동안 일시 중지됩니다. 여기서 await
는 coro1
의 실행을 일시 중단하고 CPU를 이벤트 루프로 반환합니다. 이벤트 루프는 이러한 2초 이내에 coro2
와 같은 다른 실행 가능한 작업을 예약합니다. "start coro2"를 인쇄한 후 coro2
함수는 await asyncio.sleep(1)
을 통해 1초 동안 일시 중지된 다음 "end coro2"를 인쇄합니다. coro2
가 일시 중지되었을 때 이벤트 루프에 다른 실행 가능한 작업이 없으면 coro2
의 일시 중지 시간이 끝날 때까지 기다렸다가 coro2
의 나머지 코드를 계속 실행합니다. coro1
과 coro2
가 모두 실행되면 이벤트 루프가 종료됩니다.
3.4 실행 결과 및 분석
결과:
start coro1
start coro2
end coro2
end coro1
coro1
이 await asyncio.sleep(2)
까지 실행되면 프로세스가 일시 중단되고 CPU가 이벤트 루프에 반환되어 이벤트 루프의 다음 예약을 기다립니다. 이때 이벤트 루프는 coro2
를 예약하여 계속 실행합니다.
새로운 코루틴 구현을 요약하면 다음과 같습니다.
- 이벤트 루프:
asyncio
에서 제공하는loop
를 통해 달성되며, 보다 효율적이고 유연하며 많은 비동기 작업을 관리할 수 있습니다. - 프로그램 일시 중단:
await
키워드를 통해 달성되어 코루틴의 비동기 작업을 보다 직관적이고 이해하기 쉽게 만듭니다.
4. 구형 및 신형 코루틴 구현 비교
4.1 구현 메커니즘의 차이점
- yield: 제너레이터(Generator) 함수용 키워드입니다. 함수에
yield
문이 포함되어 있으면 제너레이터 객체를 반환합니다. 제너레이터 객체는next()
메서드를 호출하거나for
루프를 사용하여 제너레이터 함수에서 값을 단계별로 가져오기 위해 반복할 수 있습니다.yield
를 통해 함수를 여러 코드 블록으로 나눌 수 있으며 이러한 블록 간에 실행을 전환하여 함수 실행의 일시 중지 및 재개를 달성할 수 있습니다. - asyncio: Python에서 비동기 코드를 작성하기 위해 제공하는 표준 라이브러리입니다. 이벤트 루프(Event Loop) 패턴을 기반으로 하므로 단일 스레드에서 여러 동시 작업을 처리할 수 있습니다.
asyncio
는async
및await
키워드를 사용하여 코루틴 함수를 정의합니다. 코루틴 함수에서await
키워드는 현재 코루틴의 실행을 일시 중지하고 비동기 작업이 완료될 때까지 기다린 다음 실행을 재개하는 데 사용됩니다.
4.2 차이점 요약
- 구형 코루틴: 주로
yield
키워드의 실행 일시 중지 및 재개 기능을 통해 코루틴을 달성합니다. 장점은 제너레이터에 익숙한 개발자의 경우 제너레이터 구문을 기반으로 이해하기 쉽다는 것입니다. 단점은 제너레이터 개념과 혼동하기 쉽고 이벤트 루프를 수동으로 작성하는 방법이 유연하고 효율적이지 않다는 것입니다. - 신형 코루틴: 프로세스를 일시 중단하는
await
키워드의 기능과 결합된 이벤트 루프 메커니즘을 통해 코루틴을 달성합니다. 장점은 보다 강력하고 유연한 비동기 프로그래밍 기능을 제공하고 코드 구조가 더 명확하며 최신 비동기 프로그래밍의 요구 사항을 더 잘 충족한다는 것입니다. 단점은 초보자의 경우 이벤트 루프 및 비동기 프로그래밍의 개념이 상대적으로 추상적일 수 있으며 이해하고 마스터하는 데 시간이 걸릴 수 있습니다.
5. await
와 yield
의 관계
5.1 유사점
- 제어 흐름 일시 중지 및 재개:
await
와yield
모두 특정 시점 또는 후에 코드 실행을 일시 중지하고 계속할 수 있는 기능이 있습니다. 이 특성은 비동기 프로그래밍 및 제너레이터 프로그래밍에서 중요한 역할을 합니다. - 코루틴 지원: 둘 다 코루틴(Coroutine)과 밀접한 관련이 있습니다. 이를 사용하여 코루틴을 정의하고 관리하여 비동기 코드 작성을 더 간단하고 읽기 쉽게 만들 수 있습니다. 구형 코루틴이든 신형 코루틴이든 이러한 두 키워드에 의존하여 코루틴의 핵심 기능을 달성합니다.
5.2 차이점
- 구문 차이:
await
키워드는 Python 3.5에 도입되었으며 비동기 함수에서 실행을 일시 중지하고 비동기 작업이 완료될 때까지 기다리는 데 구체적으로 사용됩니다.yield
키워드는 초기 코루틴용이며 주로 제너레이터(Generator) 함수에서 반복기를 만들고 지연 평가를 구현하는 데 사용됩니다. 초기 코루틴은 제너레이터의 기능을 통해 실현되었습니다. - 의미:
await
는 현재 코루틴이 비동기 작업이 완료될 때까지 기다리고 실행을 일시 중단하여 다른 작업이 실행될 기회를 줘야 함을 의미합니다. 비동기 작업의 결과를 기다리는 것을 강조하며 비동기 프로그래밍의 대기 메커니즘입니다.yield
는 함수의 상태를 저장하면서 실행 제어를 호출자에게 넘겨주어 다음 반복에서 일시 중지된 위치에서 실행을 재개할 수 있도록 합니다. 함수 실행의 제어 및 상태 보존에 더 중점을 둡니다.await
는 프로그램을 일시 중단하고 이벤트 루프가 새 작업을 예약하도록 합니다.yield
는 프로그램을 일시 중단하고 호출자의 다음 지시를 기다립니다.
- 컨텍스트:
await
는 비동기 함수 또는async with
블록과 같이 비동기 컨텍스트에서 사용해야 합니다.yield
는 코루틴을 사용하는 컨텍스트가 없더라도 함수가 제너레이터 함수로 정의되어 있는 한 일반 함수에서 사용할 수 있습니다. - 반환 값:
yield
는 제너레이터 객체를 반환하며, 제너레이터의 값은next()
메서드를 호출하거나for
루프를 사용하여 단계별로 반복할 수 있습니다.await
키워드는 Future, Task, Coroutine 등 비동기 작업의 결과 또는 상태를 나타내는 awaitable 객체(Awaitable)를 반환합니다.
5.3 요약
await
는 yield
를 통해 프로그램 일시 중지 및 실행을 구현하지 않습니다. 유사한 기능이 있지만 호출 관계가 전혀 없으며 둘 다 Python 키워드입니다. await
는 비동기 프로그래밍 시나리오에 적합하고 비동기 작업이 완료될 때까지 기다리는 데 사용되며 보다 유연한 코루틴 관리를 지원합니다. yield
는 주로 제너레이터 함수에서 반복기 및 지연 평가를 구현하는 데 사용됩니다. 애플리케이션 시나리오와 구문에는 약간의 차이가 있지만 둘 다 제어 흐름을 일시 중지하고 재개하는 기능을 제공합니다.
Python 코루틴의 개발 프로세스를 검토하고, 신형 및 구형 코루틴의 구현 방법을 비교하고, await
와 yield
의 관계를 심층적으로 분석함으로써 Python 코루틴의 원리에 대해 보다 포괄적이고 심층적인 이해를 갖게 되었습니다. 이러한 내용을 이해하기는 다소 어렵지만 이를 마스터하면 Python 비동기 프로그래밍 분야에서 탐색을 위한 견고한 기반을 마련할 수 있습니다.
Leapcell: Python 앱 호스팅을 위한 최고의 서버리스 플랫폼
마지막으로 Python 애플리케이션을 배포하는 데 가장 적합한 플랫폼인 Leapcell을 소개합니다.
1. 다중 언어 지원
- JavaScript, Python, Go 또는 Rust로 개발합니다.
2. 무제한 프로젝트를 무료로 배포
- 사용량에 대해서만 비용을 지불합니다. 요청도 없고 요금도 없습니다.
3. 최고의 비용 효율성
- 유휴 요금 없이 사용한 만큼만 지불합니다.
- 예: $25는 평균 응답 시간 60ms에서 694만 건의 요청을 지원합니다.
4. 간소화된 개발자 경험
- 손쉬운 설정을 위한 직관적인 UI입니다.
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합입니다.
- 실행 가능한 통찰력을 위한 실시간 메트릭 및 로깅입니다.
5. 손쉬운 확장성 및 고성능
- 높은 동시성을 쉽게 처리할 수 있도록 자동 조정합니다.
- 운영 오버헤드가 전혀 없습니다. 구축에만 집중하십시오.
Leapcell Twitter: https://x.com/LeapcellHQ