속도는 얻었는데 코드가 읽기 어려워졌다면, 둘 중 하나는 포기해야 할까요? 일하는 방식은 그대로 두고, 업무 일지만 위에서 아래로 읽히게 다시 쓰는 방법이 있습니다.
기초에서 먼저 보기이벤트루프는 언제 유리하고 언제 불리할까?콜백 방식은 잘 돌지만 코드가 금세 읽기 어려워집니다. 조회하고, 그 결과로 또 조회하고, 실패하면 되돌리는 흐름을 콜백으로 쓰면 중첩이 깊어지고 에러 처리가 흩어집니다. 코루틴은 이 문제를 문법으로 해결합니다 — 실행 방식은 이벤트루프 그대로 두고, 코드 모양만 동기처럼 되돌립니다.
아래 코드의 await 지점에서 벌어지는 일은 11단원과 똑같습니다. 콜백을 등록하고 즉시 반환합니다. 다만 그 등록을 우리가 손으로 쓰는 대신 컴파일러나 런타임이 대신 해줍니다. IO가 끝나면 멈췄던 그 지점부터 이어서 실행됩니다.
보통의 함수는 한 번 시작하면 return까지 쭉 달립니다. 코루틴은 중간에 멈추고(suspend) 나중에 그 자리에서 다시 시작할(resume) 수 있는 함수입니다. 멈춘 동안 지역변수와 진행 위치는 어딘가에 보관됩니다 — 6단원의 문맥 저장과 비슷한 발상이지만, 운영체제가 아니라 언어 런타임이 훨씬 가볍게 처리합니다.
코루틴을 아무리 많이 만들어도 그것을 실행하는 스레드는 소수입니다. 코루틴 하나는 스택 한 벌이 아니라 "멈춘 위치와 지역변수 묶음" 정도라서 훨씬 가볍습니다. 그래서 코루틴 수만 개를 만들어도 10단원의 스레드 수만 개와는 비교할 수 없이 적은 메모리를 씁니다.
자바스크립트와 파이썬의 async/await, 코틀린의 suspend 함수, 고의 고루틴이 모두 같은 아이디어입니다. 아래 애니메이션이 11단원과 완전히 똑같이 움직이는 것을 확인해 보세요 — 실행 모델은 그대로이고 문법만 바뀌었기 때문입니다.
코루틴은 만능이 아닙니다. 세 경우에는 오히려 손해입니다. 첫째, CPU를 오래 쓰는 계산입니다 — 중단점이 없으니 다른 코루틴이 끼어들 수 없습니다. 둘째, 블로킹 라이브러리를 부르는 경우입니다. 셋째, 애초에 동시에 할 일이 없는 순수 순차 로직입니다. 이때는 얻는 것 없이 스택 추적만 끊기고 디버깅이 어려워집니다.
async 함수는 async 함수 안에서만 자연스럽게 부를 수 있습니다. 그래서 코드베이스 한 곳을 비동기로 바꾸면 그 호출 사슬 전체가 비동기로 물듭니다. 이것을 함수의 색깔 문제라고 부릅니다. 가상 스레드가 주목받은 이유가 여기 있습니다 — 코드에 색깔을 칠하지 않고도 같은 효과를 내기 때문입니다.
1async handle(req) {2 id = parse(req)3 user = await db.query(id)await = 등록+반환, 끝나면 여기서 재개4 return toJson(user)5}
시뮬레이터에서 해보기
이 단원의 용어
왜 이렇게 발전했나
사람은 위에서 아래로 읽는 코드를 훨씬 잘 다룹니다. 성능을 위해 가독성을 포기해야 한다면 대부분의 팀은 결국 버그를 만듭니다. 그래서 언어들이 차례로 코루틴을 문법에 들여왔습니다 — 성능은 이벤트루프의 것, 읽기는 동기 코드의 것.
문법은 동기처럼, 실행은 이벤트루프처럼. 마법이 아니라 문법 설탕.
남은 문제
그래도 약점이 남았습니다. 이벤트루프는 모두가 빨리 양보한다는 약속 위에서 돕니다. 양보하지 않는 코드가 오면 어떻게 될까요? →