[PY-010][실무] async와 await로 기다림 겹쳐 처리하기 > IT 기술 공유

본문 바로가기
사이트 내 전체검색

IT 기술 공유

[PY-010][실무] async와 await로 기다림 겹쳐 처리하기

페이지 정보

profile_image
작성자 기술팀장
댓글 0건 조회 124회 작성일 26-09-04 17:35

본문

[이번 수업]
Python의 `async`와 `await`로 네트워크처럼 기다림이 많은 I/O 작업을 겹쳐 처리합니다. 외부 서버 없이 내 컴퓨터 안의 작은 TCP 통신으로 흐름과 자원 정리를 익힙니다.

[선수지식]
함수, 예외, 바이트와 문자열의 차이를 알고 터미널에서 Python을 실행할 수 있어야 합니다.

[학습목표]
1. 코루틴과 이벤트 루프의 역할을 설명합니다.
2. 스트림을 비동기로 읽고 쓰며 연결을 닫습니다.
3. 작업 수, 시간 제한, 예외를 통제합니다.

[핵심개념]
`async def`로 만든 함수는 코루틴 함수입니다. 호출하면 실행 결과 대신 코루틴 객체가 생기며 `await`나 `gather`로 실행 기회를 주어야 합니다. 이벤트 루프는 준비된 작업을 실행하다 I/O를 기다리는 `await`를 만나면 다른 작업으로 전환합니다. `asyncio.run(main())`은 최상위 코루틴과 루프를 실행합니다.

`await`는 새 스레드를 만드는 명령이 아니라 현재 코루틴이 제어권을 양보하는 문법입니다. `time.sleep`이나 오래 걸리는 일반 함수는 루프를 막습니다. 비동기 코드에서는 `asyncio.sleep`을 쓰고 CPU 계산은 별도 프로세스를 검토합니다.

스트림 API의 `StreamReader`와 `StreamWriter`로 연결을 읽고 씁니다. `write` 뒤 `await drain()`으로 쓰기 흐름 제어에 협력하고, 사용 후 `close()`와 `await wait_closed()`로 정리합니다. `gather`는 완료 시각과 관계없이 전달한 순서대로 결과를 돌려줍니다.

실무에서는 `Semaphore`로 동시 수를 제한하고 `wait_for`나 timeout으로 최대 대기 시간을 둡니다. 취소와 예외가 발생해도 `finally`에서 연결을 닫고 예외를 숨기지 않습니다.

[따라하기]
다음을 async_echo.py로 저장합니다. 127.0.0.1은 내 컴퓨터만, 포트 0은 사용 가능한 임시 포트를 뜻합니다.

```python
import asyncio

async def handle(reader, writer):
    try:
        line = await reader.readline()
        await asyncio.sleep(0.2)
        writer.write(b"echo:" + line)
        await writer.drain()
    finally:
        writer.close()
        await writer.wait_closed()

async def client(port, text):
    reader, writer = await asyncio.open_connection("127.0.0.1", port)
    try:
        writer.write((text + "\n").encode())
        await writer.drain()
        return (await reader.readline()).decode().strip()
    finally:
        writer.close()
        await writer.wait_closed()

async def main():
    server = await asyncio.start_server(handle, "127.0.0.1", 0)
    port = server.sockets[0].getsockname()[1]
    loop = asyncio.get_running_loop()
    started = loop.time()
    async with server:
        results = await asyncio.gather(
            client(port, "hello"),
            client(port, "world"),
        )
    print("결과:", results)
    print(f"시간: {loop.time() - started:.1f}초")

asyncio.run(main())
```

macOS/Linux는 `python3 async_echo.py`, Windows PowerShell은 `py async_echo.py`로 실행합니다. 패키지나 인터넷은 필요 없습니다. 시간은 조금 달라도 핵심 결과는 다음과 같습니다.

```text
결과: ['echo:hello', 'echo:world']
시간: 0.2초
```

요청마다 0.2초를 기다리지만 두 클라이언트가 겹쳐 약 0.2초가 걸립니다. 서버와 각 writer는 문맥 관리자와 `finally`에서 닫힙니다.

[흔한 실수]
코루틴을 호출만 하고 `await`하지 않거나 비동기 함수 안에서 `time.sleep`을 쓰기 쉽습니다. 빈 `except`로 예외를 숨기거나 연결을 닫지 않는 것도 피합니다.

[보안 주의]
서버는 외부에 열리는 `0.0.0.0`이 아니라 `127.0.0.1`에만 연결합니다. 실제 서비스는 입력 크기, 연결 수, 읽기 시간을 제한하고 수신 문자열을 명령으로 쓰지 않습니다. 비밀값을 넣지 말고 본인 소유의 로컬 또는 허가된 격리 환경에서만 실행합니다.

[직접 해볼 과제]
세 번째 `client(port, "python")`을 추가하세요. 다음에는 `gather` 대신 하나씩 `await`해 약 0.4초가 되는지 비교하고 이유를 기록합니다.

[확인문제]
1. 코루틴 함수를 호출만 했을 때 바로 실행되지 않는 이유는 무엇인가요?
2. 비동기 함수 안에서 `time.sleep`을 피해야 하는 이유는 무엇인가요?
3. 연결을 `finally`에서 닫으면 어떤 실패 상황에 도움이 되나요?

[다음 학습]
다음 과정 JSTS-010에서는 테스트·린트·포맷 자동화를 배웁니다. PY-011에서는 pytest로 자동 테스트를 작성합니다.

[공식 참고 자료]
- Python asyncio 공식 문서: https://docs.python.org/3/library/asyncio.html
- Python 코루틴과 작업: https://docs.python.org/3/library/asyncio-task.html
- Python 스트림 공식 문서: https://docs.python.org/3/library/asyncio-stream.html
- Python 동기화 기본 도구: https://docs.python.org/3/library/asyncio-sync.html

댓글목록

등록된 댓글이 없습니다.

회원로그인

회원가입

사이트 정보

회사명 : 회사명 / 대표 : 대표자명
주소 : OO도 OO시 OO구 OO동 123-45
사업자 등록번호 : 123-45-67890
전화 : 02-123-4567 팩스 : 02-123-4568
통신판매업신고번호 : 제 OO구 - 123호
개인정보관리책임자 : 정보책임자명

접속자집계

오늘
4,513
어제
6,862
최대
16,772
전체
770,464
Copyright © 소유하신 도메인. All rights reserved.