비동기 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

