[4편] Python 비동기: async/await의 탄생과 asyncio 기본

정재혁·2025년 8월 11일

1. 들어가며

이전 1~3편에서 우리는

  • 동기 vs 비동기, blocking vs non-blocking (OS 관점)
  • iterator와 generator
  • 향상된 generator와 coroutine (PEP 342)

까지 살펴봤습니다.

이제 Python 3.5에서 등장한 async / await 문법으로 넘어갈 차례입니다.

2. async/await가 나오게 된 배경

2.1 기존 비동기 문법의 한계

Python 3.4까지 비동기 코드는 주로 generator 기반 coroutine
@asyncio.coroutine + yield from 구문을 사용했습니다.

@asyncio.coroutine
def fetch_data():
    yield from asyncio.sleep(1)
    return "data"

문제점:

yield from 과 일반 generator의 구분이 모호함
yield는 값을 생성하고, yield from은 coroutine 위임을 위해 사용되었습니다.
전혀 다른 목적을 위해 yield 가 사용되고 있는 것입니다.

데코레이터(@asyncio.coroutine) 필수
이 함수가 coroutine인지 generator인지 바로 알기가 어렵습니다
단순 yeild라면 generator고 yeild from 이라면 corutine 입니다.
이를 구분하기 위해서 데코레이터를 필수로 달아줘야 했습니다

불편함
JavaScript, C#, Kotlin 등은 이미 우리에게 익숙한 async/await 형태를 사용하고 있었습니다

2.2 PEP 492: Coroutines with async and await syntax

PEP 492는 Python 3.5에서 async / await 키워드를 도입하며
기존 @asyncio.coroutine + yield from 방식을 대체했습니다.

PEP492의 abstraction을 봅시다

인터넷과 네트워크 환경이 발전함에 따라 반응성이 높고 확장성 있는 코드를 작성해야 할 필요성이 커지고 있습니다.
이 PEP의 목표는 Python에서 비동기(asynchronous)와 동시성(concurrent) 코드를 보다 쉽고, 더 Pythonic하게 작성할 수 있도록 하는 것입니다.
이를 위해 coroutine을 Python의 독립적인 핵심 개념으로 격상시키고, 이를 지원하는 새로운 문법을 도입합니다.
최종적으로는 비동기 프로그래밍을 위한 공통적이면서도 이해하기 쉬운 사고 체계를 마련하고, 그 사용 경험을 가능한 한 동기(synchronous) 프로그래밍과 유사하게 만드는 것을 지향합니다.

abstract를 보면 async/await 키워드의 목적을 잘 알 수 있습니다.

비동기와 동시성 코드를 보다 쉽고, 더 pythonic 작성할 수 있게 한다
이를 위해서, 아래 내용을 제안하는 군요

"coroutine을 python의 독립적인 핵심 개념으로 격상하고, 이를 지원하는 문법을 도입한다"

그러면 pep492에서 제안한 문법이 뭔지 볼까요?

3. 새로운 문법으로 개선된 예시

기존 방식 (Python 3.4)

import asyncio

@asyncio.coroutine
def old_style():
    yield from asyncio.sleep(1)
    return "done"

loop = asyncio.get_event_loop()
result = loop.run_until_complete(old_style())
print(result)

개선된 방식 (Python 3.5+)

import asyncio

async def new_style():
    await asyncio.sleep(1)
    return "done"

result = asyncio.run(new_style())
print(result)

차이점

  • @asyncio.coroutineasync def
  • yield fromawait
    함수 정의만 봐도 coroutine이라는 것을 명확하게 알 수 있지 않나요?
    사실 javascript 또는 다른 언어의 비동기에 익숙하다면 아 비동기 함수로군! 하고 알 수 있을겁니다

4. asyncio와 async/await 기본 사용

4.1 async 함수

async def hello():
    return "Hello Async"

print(hello())  # <coroutine object hello at 0x...>
  • async def로 정의된 함수는 즉시 실행되지 않고 coroutine 객체를 반환

4.2 await

import asyncio

async def say_hello():
    await asyncio.sleep(1)
    print("Hello World")

asyncio.run(say_hello())
  • 다른 coroutine이나 awaitable 객체를 기다리는 키워드

5. 예제: 동시에 실행되는 비동기 작업

import asyncio

async def worker(name):
    print(f"{name} 시작")
    await asyncio.sleep(1)
    print(f"{name} 완료")

async def main():
    await asyncio.gather(
        worker("작업1"),
        worker("작업2")
    )

asyncio.run(main())

출력 예시

작업1 시작
작업2 시작
작업1 완료
작업2 완료
  • 두 작업이 거의 동시에 시작되고 1초 뒤 함께 끝남

6. 마무리

async/await는 단순히 쓰기 편한 문법이 아니라, 비동기 코드의 가독성과 명확성을 높이는 언어 차원의 개선이였습니다

다음 5편에서는 async/await가 실제로 동작하는 기반인 이벤트 루프(Event Loop) 구조를 깊이 살펴보겠습니다.

profile
궁금한 것을 궁금한 것으로 두지 말자.

0개의 댓글