TECH 으로 돌아가기
TECH HACKER NEWS 오늘 8분 읽기 20 READS

async/await는 왜 언어마다 다르게 생겼을까: 비동기 설계의 선택지 총정리

async/await는 왜 언어마다 다르게 생겼을까: 비동기 설계의 선택지 총정리
SOURCE IMAGE · HACKER NEWS
async/await는 왜 언어마다 다르게 생겼을까: 비동기 설계의 선택지 총정리

무슨 일이 있었나요

브라운대학교 인지공학연구실(CEL, Cognitive Engineering Lab) 블로그에 async/await의 설계 공간을 탐색하는 글이 올라왔어요. 이 연구실은 Rust 언어의 학습 난이도를 연구해온 윌 크라이턴(Will Crichton) 등이 있는 곳인데요, 프로그래밍 언어를 “사람이 어떻게 이해하는가”라는 관점에서 분석하는 걸로 유명해요.

“설계 공간 탐색(design space exploration)”이라는 말이 낯설 수 있는데요. 이게 뭐냐면, 어떤 기능을 만들 때 내릴 수 있는 결정들을 하나하나 축으로 놓고, 각 축에서 어떤 선택지가 있고 그 선택이 다른 축에 어떤 영향을 주는지 지도를 그리는 작업이에요. async/await는 자바스크립트, 파이썬, C#, 러스트, 코틀린, 스위프트 등 거의 모든 주류 언어에 있지만, 겉모양만 비슷할 뿐 속은 전부 다르거든요. 이 글은 “왜 다른지”를 축별로 설명해줘요.

핵심 내용: 비동기 설계의 주요 축들

제가 이해한 내용을 바탕으로 핵심 축들을 풀어볼게요.

1. 스택리스 vs 스택풀 코루틴

코루틴은 중간에 멈췄다가 나중에 이어서 실행할 수 있는 함수예요. 이걸 구현하는 방법이 두 가지인데요. 스택풀(stackful) 방식은 각 코루틴에 자기만의 실행 스택을 통째로 주는 거예요. Go의 고루틴이 대표적이죠. 어디서든 멈출 수 있어서 편하지만 스택 메모리를 따로 관리해야 해요. 스택리스(stackless) 방식은 컴파일러가 함수를 상태 머신으로 바꿔서, 멈추는 지점(await)마다 필요한 변수만 객체에 저장해두는 방식이에요. 러스트, C#, 자바스크립트가 이쪽이에요. 메모리를 아낄 수 있지만 “await는 async 함수 안에서만 쓸 수 있다”는 제약이 생겨요.

2. 함수 색깔 문제

“What Color is Your Function?”이라는 유명한 글에서 나온 개념인데요. async 함수는 async 함수에서만 호출할 수 있으니, 코드베이스가 “빨간 함수(async)”와 “파란 함수(동기)”로 갈라진다는 비판이에요. Go나 자바의 가상 스레드(Virtual Threads)는 이 문제를 아예 없앴고, 러스트나 자바스크립트는 감수하기로 한 거예요. 글은 이게 단순히 나쁜 게 아니라, 어디서 실행이 멈출 수 있는지 코드에 명시적으로 드러난다는 장점과 맞바꾼 결정이라고 설명해요.

3. 누가 실행하는가: 런타임 내장 vs 외부 실행기

자바스크립트는 이벤트 루프가 언어 런타임에 내장돼 있어서 Promise가 알아서 실행돼요. 반면 러스트는 언어에 실행기(executor)가 없어요. Future는 그냥 “poll 해주면 진행되는 객체”일 뿐이고, tokio 같은 라이브러리가 실행을 담당해요. 이 선택 때문에 러스트는 임베디드부터 서버까지 같은 문법을 쓸 수 있지만, 초보자는 “왜 async 함수를 호출했는데 아무 일도 안 일어나지?”라는 혼란을 겪어요.

4. 취소와 구조화된 동시성

비동기 작업을 중간에 어떻게 취소하느냐도 큰 축이에요. 코틀린과 스위프트는 구조화된 동시성(structured concurrency)이라는 모델을 택했는데요. 이게 뭐냐면, 자식 작업은 부모 작업의 범위 안에서만 살 수 있고, 부모가 끝나거나 취소되면 자식도 자동으로 정리되는 규칙이에요. 마치 함수의 지역 변수가 함수가 끝나면 사라지는 것처럼요. 반면 자바스크립트의 Promise는 한 번 만들면 취소할 방법이 없어서 AbortController 같은 별도 장치가 필요해요. 러스트는 Future를 그냥 drop하면 취소되지만, 그래서 “취소 안전성(cancellation safety)”이라는 또 다른 골칫거리가 생기죠.

5. 완료 통지 방식: 콜백 vs 폴링

자바스크립트 Promise는 완료되면 등록된 콜백을 불러줘요(push 방식). 러스트 Future는 실행기가 “다 됐어?”라고 계속 물어보는 방식이에요(pull 방식). 폴링 방식은 추가 할당 없이 동작할 수 있어서 성능에 유리하지만, Pin이나 Waker 같은 어려운 개념이 필요해져요.

업계 맥락

지금 업계는 크게 두 흐름으로 나뉘어 있어요. 하나는 자바의 가상 스레드나 Go처럼 “동기 코드처럼 쓰되 런타임이 알아서 스케줄링”하는 방향이고, 다른 하나는 러스트처럼 “비용을 코드에 명시하고 제어권을 개발자에게” 주는 방향이에요. 자바 21 이후 가상 스레드가 안정화되면서 “async/await가 정말 필요했나?”라는 논쟁도 다시 불붙었죠. 러스트 커뮤니티에서도 async 생태계의 복잡함이 오랜 고민거리라서, 이런 설계 공간 정리는 다음 언어 설계에 참고 자료가 될 거예요.

한국 개발자에게 주는 시사점

당장 실무에 적용할 코드는 아니지만, 이 글은 “왜 내가 쓰는 언어의 async가 이렇게 생겼는지”를 이해하게 해줘요. 코틀린 코루틴을 쓰다가 러스트 tokio로 넘어갈 때 겪는 혼란, 자바스크립트에서 Promise 취소가 안 돼서 고생한 경험, 파이썬 asyncio에서 동기 라이브러리를 섞다가 이벤트 루프가 막힌 경험. 이 모든 게 설계 축 위의 특정 선택에서 비롯된 거거든요. 언어를 하나 더 배울 때 이 축들을 기준으로 보면 훨씬 빨리 감을 잡을 수 있어요.

마무리

한 줄로 정리하면, async/await는 하나의 기능이 아니라 여러 설계 결정의 조합이고, 언어마다 다른 조합을 골랐을 뿐이라는 거예요.

여러분이 주로 쓰는 언어의 비동기 모델에서 가장 불편한 점은 뭐였나요? 그리고 가상 스레드처럼 “색깔 없는 함수” 모델이 결국 async/await를 대체할 거라고 보시나요?


🔗 출처: Hacker News

SOURCE · HACKER NEWS
원문 전체 보기 → https://cel.cs.brown.edu/blog/design-space-async-await/
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기
처리 중...