코루틴
코루틴 (Coroutines)
루아는 코루틴(coroutine)을 지원해요. 코루틴은 "협력적(cooperative) 멀티태스킹"의 일종으로, 실행을 중단(suspend)했다가 재개(resume)할 수 있는 독립된 실행 흐름이에요. 루아에서 코루틴은 thread 타입으로 표현되고, coroutine 라이브러리로 조작돼요.
루아의 코루틴은 **스택풀 코루틴(stackful coroutine)**이에요. 즉, 각 코루틴은 자신만의 호출 스택을 가져서, 중첩된 함수 호출 안에서도 자유롭게 양보(yield)할 수 있어요. yield는 코루틴이 어디에 있든 (함수 안 깊숙이 있어도) 실행을 중단시킬 수 있고, resume은 그 지점부터 다시 이어서 실행 가능해요.
코루틴과 일반 함수의 핵심 차이는, 코루틴이 완료 후에 도달 가능한 여러 중단 지점을 가질 수 있고, 각 중단/재개 사이에 자신의 상태(지역 변수, 스택)를 유지한다는 점이에요.
기본 동작
코루틴을 만들고 사용하는 기본 함수는 다음과 같아요.
coroutine.create(f)— 함수f를 본문으로 하는 새 코루틴을 만들어요. 만들어진 직후의 코루틴은 "중단됨(suspended)" 상태예요.coroutine.resume(co, ...)— 코루틴co를 재개해요. 중단됨 상태였다면 첫 번째 실행을 시작하고, 양보된 상태였다면 그 양보 지점부터 이어서 실행해요.coroutine.yield(...)— 현재 코루틴의 실행을 중단하고,resume을 호출한 쪽에 값을 넘겨요.coroutine.status(co)— 코루틴의 상태("running","suspended","normal","dead")를 반환해요.coroutine.running()— 현재 실행 중인 코루틴을 반환해요 (보통true와 함께). 메인 스레드에서 호출하면nil을 반환해요.coroutine.close(co)— 코루틴을 닫아 파괴해요.coroutine.isyieldable([co])— 코루틴이 양보 가능한지 여부를 반환해요.
local co = coroutine.create(function(a, b)
print("첫 실행:", a, b)
local x, y = coroutine.yield("중단 지점에서 반환")
print("재개 후 인자:", x, y)
return "완료"
end)
print(coroutine.status(co)) --> suspended
print(coroutine.resume(co, 1, 2)) --> 첫 실행: 1 2 / true 중단 지점에서 반환
print(coroutine.resume(co, 3, 4)) --> 재개 후 인자: 3 4 / true 완료
resume은 성공 시 true를, 실패 시 false와 오류 메시지를 반환해요. yield의 반환값은 다음 resume이 넘긴 인자들이에요.
값 전달과 반환
yield로 넘긴 값들은 resume의 반환값이 되어 호출자에게 전달돼요. 반대로 resume으로 넘긴 인자들은 yield의 반환값이 되어 코루틴 안에서 받아져요. 코루틴이 본문 함수를 모두 실행하고 return하면, 그 반환값들이 최종 resume의 반환값이 돼요.
local co = coroutine.create(function()
local a = coroutine.yield(10, 20) -- 10과 20을 resume 호출자에게 전달
return a * 2
end)
local ok, v1, v2 = coroutine.resume(co) --> true 10 20
local ok, result = coroutine.resume(co, 21) --> true 42
에러 처리
코루틴 안에서 오류가 발생하면, 그 오류는 resume 호출이 false와 오류 객체를 반환하면서 전파돼요. resume이 오류로 실패하면 코루틴은 "죽음(dead)" 상태가 돼요.
비-양보 가능한 함수
C 함수(표준 라이브러리의 일부)는 연속 호출(continuation)을 지원하지 않는 한 양보할 수 없어요. 그런 C 함수 안에서 yield를 호출하면 오류가 발생해요. coroutine.isyieldable(co)는 그 코루틴이 현재 양보 가능한지 검사할 때 유용해요 (예: C로 작성된 라이브러리 함수).
코루틴 상태 (status)
"running"— 코루틴이 현재 실행 중."suspended"— 코루틴이 중단됨.create직후 또는yield후."normal"— 코루틴이 활성 상태이지만 실행 중이 아님 (다른 코루틴이 다시 시작됐을 때). 프로그램이yield로 다른 코루틴에 제어를 넘긴 상태."dead"— 코루틴이 본문을 마쳤거나 오류로 종료됨.
코루틴과 스레드의 차이
코루틴은 원자적(atomic)이지 않아요. 코루틴 간의 전환은 명시적인 yield와 resume 호출에 의해서만 일어나고, 다른 코루틴이 실행 중일 때 현재 코루틴은 정지돼요. 선점(preemptive) 스케줄링을 지원하지 않아, 어떤 코루틴도 다른 코루틴의 실행을 강제로 중단시키지 못해요.
코루틴은 운영체제 스레드나 프로세스와 다르게 단일 OS 스레드 안에서 협력적으로 동작해요. 따라서 빠르고 오버헤드가 적지만, 병렬 실행(parallelism)은 제공하지 않아요.
코루틴 활용
코루틴은 다음 용도에 자주 쓰여요.
- 반복자(iterator)/생성기(generator):
coroutine.wrap으로 값을 하나씩 생성하는 반복자를 쉽게 만들 수 있어요.coroutine.wrap(f)는 코루틴을 감싸 호출 가능한 함수를 반환하는데, 호출할 때마다 코루틴을resume해요. - 비동기 흐름: "협력적인 테스크"를 순차적 코드로 표현.
- 상태 머신: 명시적 상태 변수 없이 중단/재개로 상태를 유지.
- 소비자-생산자 패턴:
yield/resume으로 데이터를 주고받으며 교대 실행.
-- coroutine.wrap을 이용한 무한 시퀀스
local function counter()
return coroutine.wrap(function()
local i = 0
while true do
i = i + 1
coroutine.yield(i)
end
end)
end
local next = counter()
print(next()) --> 1
print(next()) --> 2
print(next()) --> 3
출처: 코루틴 (Coroutines)
본문
코루틴 만들기와 재개하기
coroutine.create(f)는 본문이 f인 새 코루틴을 만들어 "suspended" 상태로 두어요. coroutine.resume(co, ...)는 코루틴을 재개해요. 코루틴이 처음 재개될 때는 함수 f를 인자와 함께 호출하고, yield 후 재개될 때는 그 양보 지점에서 이어서 계속해요.
resume의 반환값은 성공 시 true 다음에 코루틴이 반환하거나 양보한 값들이에요. 코루틴이 실행을 다 마치고 반환했다면 그 값들이, 양보했다면 yield가 넘긴 값들이 돌아와요. 코루틴 안에서 오류가 발생하면 resume은 false와 오류 객체를 반환해요.
양보하기 (yield)
coroutine.yield(...)는 현재 실행 중인 코루틴의 실행을 중단하고, 그 값을 resume을 호출한 쪽에 돌려줘요. 코루틴이 다시 resume되면, yield 호출은 resume이 전달한 인자를 반환해요.
메인 스레드(독립 실행 인터프리터의 최상위)나 루아가 직접 호출한 C 함수 안에서 yield를 호출하면 오류가 발생해요. 왜냐하면 그 실행 맥락에서는 재개할 수 있는 코루틴 스택이 없기 때문이에요.
코루틴 닫기
coroutine.close(co)는 코루틴이 "suspended" 상태일 때 실행을 완료되도록 만들고, to-be-closed 변수를 정리한 뒤 코루틴을 "dead" 상태로 만들어 메모리를 해제해요. 코루틴이 이미 "dead" 상태면 아무 일도 하지 않아요.
실행 가능 여부
coroutine.isyieldable([co])는 코루틴 co가 현재 양보할 수 있는지 판단해요. 인자를 생략하면 현재 실행 중인 코루틴을 대상으로 해요. 메인 스레드나 비-양보 가능한 C 함수 안에서 호출하면 false를 반환해요.
wrap
coroutine.wrap(f)는 함수 f를 본문으로 하는 코루틴을 만들고, 그 코루틴을 재개하는 클로저(closure)를 반환해요. 클로저를 호출할 때마다 인자로 코루틴을 재개하고 그 반환값들을 돌려줘요. 차이점이라면, resume처럼 true/오류 플래그를 붙이지 않고, 오류가 나면 오류를 그대로 던진다는 점이에요. 코루틴이 다 끝나면 클로저는 오류("cannot resume dead coroutine")를 발생시켜요.