Python 코루틴 (Coroutine) 상세 설명
코루틴은 프로그래밍의 패러다임 중 하나로, 서브루틴(함수)을 일반화한 개념입니다. 단순히 순차적으로 실행되는 함수와 달리, 코루틴은 실행을 일시 중단(pause)했다가 나중에 다시 재개(resume)할 수 있는 능력을 핵심으로 가집니다. 이러한 특징 덕분에 코루틴은 비동기 프로그래밍과 동시성 프로그래밍에서 매우 강력한 도구로 활용됩니다.
1. 코루틴이란 무엇인가? (서브루틴과의 비교)
- 서브루틴 (Subroutine, 일반 함수):
- 단방향 진입/탈출 (Single entry point/exit point): 함수는 시작점에서 진입하여, 종료점에서 탈출합니다.
- 호출자-피호출자 관계 (Caller-Callee): 함수를 호출하는 쪽(caller)과 호출되는 쪽(callee)의 관계가 명확합니다. 함수는 호출될 때 실행되고, 완료되면 결과를 반환하고 호출자에게 제어권을 넘깁니다.
- 실행 흐름: 함수는 호출되면 시작부터 끝까지 순차적으로 실행됩니다. 중간에 멈추거나 다른 함수에게 제어권을 양보하지 않습니다.
- 코루틴 (Coroutine):
- 다중 진입/탈출 (Multiple entry/exit points): 코루틴은 실행을 일시 중단할 수 있으며, 나중에 중단된 지점부터 다시 실행을 재개할 수 있습니다. 이는 함수가 여러 번 진입점과 탈출점을 가질 수 있다는 의미입니다.
- 대등한 관계 (Cooperative): 코루틴은 서로 협력적인 관계를 가집니다. 하나의 코루틴이 실행을 일시 중단하고 다른 코루틴에게 제어권을 넘겨줄 수 있습니다. 호출자-피호출자 관계보다는 동등한 레벨에서 협력하는 관계에 가깝습니다.
- 실행 흐름: 코루틴은 시작될 때 실행되다가, 특정 시점에서 스스로 실행을 일시 중단하고, 나중에 다시 재개되어 중단된 지점부터 실행을 이어갑니다. 실행 흐름이 순차적이지 않고 중간에 끊겼다가 이어지는 특징을 가집니다.
2. 코루틴의 핵심 특징
- 일시 중단 및 재개 (Pause and Resume): 코루틴의 가장 중요한 특징입니다. 실행 중에 특정 시점에서 스스로를 멈추고, 나중에 다시 멈춘 지점부터 실행을 이어갈 수 있습니다. 이것이 비동기 프로그래밍의 핵심 메커니즘을 제공합니다.
- 비선점형 멀티태스킹 (Cooperative Multitasking): 코루틴은 스스로 제어권을 양보해야 실행이 중단됩니다. 운영체제가 강제로 시간을 분할하여 쓰레드를 선점하는 선점형 멀티태스킹과는 다릅니다. 코루틴은 프로그래머가 명시적으로 제어권을 넘겨주는 시점을 결정할 수 있습니다.
- 싱글 스레드 동시성 (Single-threaded Concurrency): 코루틴은 주로 싱글 스레드 환경에서 동시성을 구현하는 데 사용됩니다. 여러 코루틴이 싱글 스레드 내에서 번갈아 가며 실행되면서 마치 동시에 여러 작업을 처리하는 것처럼 보이게 만듭니다. 실제 병렬성(Parallelism)과는 다르지만, I/O 바운드 작업에서는 매우 효율적인 방식입니다.
3. Python 코루틴 (async/await
) 작동 방식
Python에서는 async/await
문법을 통해 코루틴을 효과적으로 구현하고 사용할 수 있습니다.
async def
: 함수를 코루틴으로 정의합니다.def
대신async def
키워드를 사용하면 해당 함수는 코루틴 함수가 됩니다. 코루틴 함수 안에서는await
키워드를 사용할 수 있습니다.await
: 코루틴 함수 안에서await
키워드를 사용하면, 다음과 같은 일이 발생합니다.- 현재 코루틴 일시 중단:
await
를 만난 코루틴은 실행을 멈춥니다. - 제어권을 이벤트 루프에 양도: 현재 코루틴은 이벤트 루프에게 제어권을 넘겨줍니다.
awaitable
객체 대기:await
뒤에 오는 객체 (다른 코루틴, Task, Future 등awaitable
객체) 가 완료될 때까지 기다립니다.- 코루틴 재개:
awaitable
객체가 완료되면, 이벤트 루프는 일시 중단되었던 코루틴을 다시 재개시켜 실행을 이어갑니다.
- 현재 코루틴 일시 중단:
- 이벤트 루프 (Event Loop):
asyncio
라이브러리의 핵심 구성 요소입니다. 이벤트 루프는 코루틴의 실행을 스케줄링하고 관리하는 역할을 합니다.await
에 의해 코루틴이 일시 중단되면, 이벤트 루프는 다른 준비된 코루틴을 실행하거나 I/O 작업을 처리합니다.await
된 작업이 완료되면, 이벤트 루프는 해당 코루틴을 다시 실행 가능 상태로 만들고, 적절한 시점에 다시 실행을 재개합니다.
4. 코루틴의 장점
- 향상된 I/O 바운드 작업 성능: I/O 작업 대기 시간을 효율적으로 활용하여 프로그램의 전체적인 처리량을 높입니다. 네트워크 요청, 파일 처리, 데이터베이스 쿼리 등 시간이 걸리는 I/O 작업을 비동기적으로 처리하여 성능을 향상시킵니다.
- 향상된 동시성 및 응답성: 싱글 스레드 환경에서도 높은 동시성을 제공하여, 많은 클라이언트 요청을 처리해야 하는 서버 애플리케이션이나 실시간 웹 애플리케이션 개발에 적합합니다. UI가 멈추지 않고 계속 응답하는 반응형 애플리케이션 개발에도 유용합니다.
- 비동기 코드 가독성 및 유지보수성 향상:
async/await
문법을 통해 복잡한 비동기 코드를 순차적인 동기 코드처럼 작성할 수 있어서, 코드의 가독성과 유지보수성을 크게 향상시킵니다. 콜백 기반 방식의 복잡함과 콜백 헬 문제를 해결해줍니다.
Python await
await
란 무엇일까요?
await
는 async def
로 정의된 코루틴 함수 안에서만 사용할 수 있는 키워드입니다. await
는 "여기서 잠시 멈춰서, 내가 기다리는 작업이 끝날 때까지 기다려줘!" 라고 프로그램에게 말하는 것과 같습니다.
await
는 무슨 일을 할까요?
await
키워드를 만나면, 다음과 같은 일이 벌어집니다.
- 현재 코루틴 일시 중단 (Pause):
await
를 사용한 코루틴은 실행을 일시적으로 멈춥니다. 마치 일시 정지 버튼을 누른 것처럼요. ⏸️ - 제어권을 이벤트 루프에게 양도 (Yield Control): 일시 중단된 코루틴은 프로그램의 제어권을 이벤트 루프에게 넘겨줍니다. "이제부터는 이벤트 루프 네가 알아서 해!" 라고 말하는 거죠. 🕹️
awaitable
객체 완료 대기 (Wait for Awaitable):await
키워드는await
뒤에 오는 객체 (주로 다른 코루틴, Task, Future 등) 가 완료될 때까지 기다립니다. 이 객체를awaitable
객체라고 부릅니다. "내가 기다리는 작업" 이 바로 이awaitable
객체를 의미합니다. ⏳- 코루틴 재개 (Resume):
awaitable
객체가 완료되면, 일시 중단되었던 코루틴은 다시 실행을 시작합니다. 멈췄던 지점부터 다시 코드를 실행하는 거죠. ▶️
await
는 왜 중요할까요? (비동기 프로그래밍의 핵심)
await
는 Python asyncio
에서 비동기 프로그래밍을 가능하게 하는 핵심입니다. await
덕분에 우리는 다음과 같은 멋진 일들을 할 수 있습니다.
- Non-blocking I/O: I/O 작업 (네트워크 요청, 파일 읽기 등) 을 기다리는 동안 프로그램이 멈추지 않고 다른 작업을 계속 할 수 있습니다. 효율적인 프로그램의 핵심이죠! 🚀
- 동시성 (Concurrency): 싱글 스레드 환경에서 여러 작업을 번갈아 가며 처리하여 동시성을 높일 수 있습니다. 마치 여러 개의 일을 동시에 하는 것처럼 느껴지게 만들죠! 👯♀️👯♂️
- 코드 가독성 향상:
async/await
문법 덕분에 복잡한 비동기 코드를 동기 코드처럼 순차적으로 작성할 수 있어서 코드 이해가 훨씬 쉬워집니다. 👍
await
비유 (식당 주문, 다시 활용!)
여러분이 식당에서 음식을 주문하고 기다리는 상황을 다시 떠올려 볼게요.
await 주문.음식준비완료()
: "주문한 음식이 준비 될 때까지 기다려줘!"- 일시 중단: 여러분은 음식이 나올 때까지 가만히 앉아 기다립니다. (코루틴 일시 중단)
- 제어권 양도: 웨이터는 여러분 테이블 말고 다른 테이블 주문도 받고, 서빙도 합니다. (이벤트 루프가 다른 작업 처리)
- 완료 대기: 주방에서 음식이 준비되는 것을 기다립니다. (awaitable 객체 완료 대기)
- 재개: 음식이 나오면, 여러분은 다시 식사를 시작합니다. (코루틴 재개)
간단한 예제 코드
import asyncio
async def 비동기_작업(이름, 시간):
print(f"{이름} 작업 시작 ({시간}초 대기)")
await asyncio.sleep(시간) # <--- await 등장!
print(f"{이름} 작업 완료")
return f"{이름} 작업 결과"
async def main():
결과1 = await 비동기_작업("작업 1", 2) # <--- await 등장!
결과2 = await 비동기_작업("작업 2", 1) # <--- await 등장!
print(f"결과 1: {결과1}")
print(f"결과 2: {결과2}")
if __name__ == "__main__":
asyncio.run(main())
예제 설명:
비동기_작업()
코루틴 안에서await asyncio.sleep(시간)
을 사용했습니다. 이await
때문에비동기_작업()
코루틴은asyncio.sleep()
이 완료될 때까지 일시 중단됩니다.main()
코루틴 안에서도await 비동기_작업(...)
을 사용해서,비동기_작업()
코루틴이 완료될 때까지 기다립니다.
실행 결과:
작업 1 작업 시작 (2초 대기)
작업 1 작업 완료
작업 2 작업 시작 (1초 대기)
작업 2 작업 완료
결과 1: 작업 1 작업 결과
결과 2: 작업 2 작업 결과
정리
await
는 Python 코루틴에서 비동기 프로그래밍의 핵심 역할을 하는 키워드입니다. await
를 사용하면 코루틴을 일시 중단하고, 제어권을 이벤트 루프에 넘겨 다른 작업을 처리할 수 있게 합니다. await
덕분에 Python asyncio
는 효율적인 비동기 I/O 프로그래밍을 가능하게 해줍니다.
예제를 통한 코루틴과 await 동작 방식의 이해
아래 예제를 통해서 코루틴과 await가 어떻게 동작하는지 이해해봅시다.
import time
import asyncio
async def first_coroutine():
print("First coroutine started")
await asyncio.sleep(1)
print("First coroutine done")
async def second_coroutine():
print("Second coroutine started")
await asyncio.sleep(1)
print("Second coroutine done")
async def example1():
print("### Example 1")
await first_coroutine()
await second_coroutine()
async def example2():
print("### Example 2")
task1 = asyncio.create_task(first_coroutine())
task2 = asyncio.create_task(second_coroutine())
print("# Tasks created")
await task1
await task2
async def example3():
print("### Example 3")
task1 = asyncio.create_task(first_coroutine())
task2 = asyncio.create_task(second_coroutine())
await asyncio.sleep(3)
print("# Tasks created")
await task1
await task2
async def example4():
print("### Example 4")
task1 = asyncio.create_task(first_coroutine())
task2 = asyncio.create_task(second_coroutine())
print("# Tasks created")
asyncio.run(example1())
asyncio.run(example2())
asyncio.run(example3())
asyncio.run(example4())
각 코루틴별 실행 결과와 그렇게 결과가 나오는 원인을 설명합니다.
Example 1
async def example1():
print("### Example 1")
await first_coroutine()
await second_coroutine()
### Example 1
First coroutine started
First coroutine done
Second coroutine started
Second coroutine done
설명: await
는 코루틴이 완료될 때까지 기다립니다. first_coroutine()
이 먼저 완전히 실행된 후, second_coroutine()
이 순차적으로 실행됩니다. 따라서 "First coroutine" 관련 메시지가 먼저 출력되고, 그 다음에 "Second coroutine" 관련 메시지가 출력됩니다.
Example 2
async def example2():
print("### Example 2")
task1 = asyncio.create_task(first_coroutine())
task2 = asyncio.create_task(second_coroutine())
print("# Tasks created")
await task1
await task2
### Example 2
# Tasks created
First coroutine started
Second coroutine started
First coroutine done
Second coroutine done
설명: asyncio.create_task()
는 코루틴을 백그라운드에서 실행하도록 예약하고 즉시 Task 객체를 반환합니다. print("# Tasks created")
는 Task 생성 직후 바로 실행됩니다. 그 후 await task1
, await task2
가 호출되어 Task들이 완료될 때까지 기다리므로, 코루틴들이 병렬적으로 실행되고, "# Tasks created"가 먼저 출력됩니다.
Example 3
async def example3():
print("### Example 3")
task1 = asyncio.create_task(first_coroutine())
task2 = asyncio.create_task(second_coroutine())
await asyncio.sleep(3)
print("# Tasks created")
await task1
await task2
### Example 3
First coroutine started
Second coroutine started
First coroutine done
Second coroutine done
# Tasks created
설명: create_task
로 코루틴들이 시작되고, await asyncio.sleep(3)
으로 3초를 기다리는 동안 백그라운드 Task들이 실행됩니다. sleep(3)
가 끝난 후 print("# Tasks created")
가 출력되지만, Task들은 이미 거의 또는 완전히 완료되었을 가능성이 높습니다. await task1
, await task2
는 이미 완료된 Task들을 기다리므로 즉시 반환됩니다.
Example 4
async def example4():
print("### Example 4")
task1 = asyncio.create_task(first_coroutine())
task2 = asyncio.create_task(second_coroutine())
print("# Tasks created")
### Example 4
# Tasks created
First coroutine started
Second coroutine started
설명: create_task
로 코루틴들이 백그라운드에서 실행되도록 예약되고, print("# Tasks created")
가 즉시 실행됩니다. example4()
함수는 await task1
, await task2
로 Task의 완료를 기다리지 않고 종료됩니다. 따라서 코루틴들은 백그라운드에서 부분적으로 실행될 수 있지만, 완료되기 전에 프로그램이 종료될 수 있습니다. 결과적으로 코루틴의 "done" 메시지는 출력되지 않았습니다.
'프로그래밍 > Python' 카테고리의 다른 글
파이썬 - 비동기 컨텍스트 매니저 async with .. as .. (0) | 2025.03.20 |
---|---|
파이썬 - Function annotation ("->") (0) | 2025.03.20 |
파이썬 - Generator & Iterator (0) | 2025.03.19 |
파이썬 - yield 키워드 (0) | 2025.03.19 |
파이썬 - pass 키워드 (0) | 2025.03.19 |