에듀테크 프로젝트 대시보드
코루틴과 제너레이터
보통 함수는 호출 → 실행 → 반환이 한 번에 끝난다. 반환하는 순간 그 함수의 지역변수·실행 위치를 담은
스택 프레임이 통째로 사라진다. 코루틴은 다르다. 중간에 yield로 실행을 멈추고
제어권을 호출자에게 돌려준 뒤, 나중에 멈춘 그 지점·그 상태 그대로 이어서 실행한다.
파이썬의 제너레이터, 자바스크립트의 function*, 그리고 그 위에 세워진 async / await가
모두 이 "멈췄다 재개" 위에서 돌아간다.
next()를 누를 때마다 한 조각씩 — 피보나치 제너레이터
아래 제너레이터는 피보나치 수를 만든다. 다음 next()를 누르면 실행이 yield 줄까지
달려가 값 하나를 뱉고 그 자리에서 얼어붙는다. 다시 누르면 얼어붙은 그 다음 줄부터 재개된다.
오른쪽 함수 프레임의 a, b가 호출 사이에도 살아남는 것에 주목하자 —
보통 함수라면 반환과 함께 지워졌을 값이다.
def fib(limit): a, b = 0, 1 while a < limit: yield a # 멈춤 지점 a, b = b, a + b # 루프 끝 → StopIteration
| 일반 함수 (서브루틴) | 코루틴 / 제너레이터 | |
|---|---|---|
| 진입점 | 항상 함수 첫 줄 | 처음엔 첫 줄, 그다음부턴 지난번 멈춘 곳 |
| 이탈점 | return 한 번 | yield가 있는 여러 곳 |
| 지역 상태 | 반환 시 파괴 | 멈춰 있는 동안 보존 |
| 호출 결과 | 값 하나 | 필요할 때마다 값의 스트림 |
| 제어 흐름 | 호출자 → 함수 → 호출자 | 서로 주고받기 (협력적) |
코루틴은 스스로 CPU를 놓아주는 협력적 멀티태스킹의 기본 단위다. OS가 강제로 스레드를 빼앗는
선점형 방식과 달리, 코루틴은 yield하는 그 순간에만 양보하므로 경쟁 상태가 생길 틈이 좁고
문맥 교환 비용도 거의 없다(스택 프레임 포인터만 바꾸면 끝).
# 파이썬 def countdown(n): while n > 0: yield n n -= 1 for x in countdown(3): # 3, 2, 1 을 하나씩 당겨 씀 print(x) // 자바스크립트 — function* 과 yield function* countdown(n) { while (n > 0) yield n--; } for (const x of countdown(3)) console.log(x);
제너레이터는 값을 미리 다 만들어 두지 않는다. 소비자가 하나 당길 때(pull)마다
파이프라인을 거슬러 올라가 딱 한 개를 길어 온다. 그래서 무한 수열도 다룰 수 있다 —
끝이 없어도 take(k)로 k개만 뽑으면 그만이니까.
아래는 자연수(1,2,3,…) → 짝수만 통과 → 제곱 → 앞에서 k개 파이프라인이다.
결과 1개 당기기를 눌러, 각 단계가 실제로 몇 번씩 돌았는지 세어 보자.
리스트로 먼저 다 만들었다면 자연수를 무한히 만들다 멈추지 못했을 것이다.
async / await는 코루틴에 이름표를 바꿔 단 것yield로 값을 내보내며 멈춘다. 재개는 next()가 시킨다.await에서 "이 작업 끝나면 깨워줘"라고 남기고 멈춘다.
I/O(네트워크·파일)가 끝나면 이벤트 루프가 그 코루틴을 멈춘 지점부터 재개한다.async),
스택풀(stackful)은 통째 스택을 저장(Go 고루틴, Lua). 전자는 가볍고, 후자는 아무 함수 깊이에서나 양보 가능.# await 지점마다 코루틴은 "멈췄다 재개"한다 async def fetch_all(urls): results = [] for u in urls: r = await http_get(u) # 여기서 양보 → 응답 오면 이 줄부터 재개 results.append(r) return results
더 깊이: 이벤트 루프 · 프로세스 vs 스레드 · 작업 훔치기 스케줄러