TECH 으로 돌아가기
TECH HACKER NEWS 오늘 7분 읽기 23 READS

같은 async/await, 다른 결과: 7개 런타임이 증명한 비동기의 분열

같은 async/await, 다른 결과: 7개 런타임이 증명한 비동기의 분열
SOURCE IMAGE · HACKER NEWS

async/await는 어느 언어에서나 거의 같은 얼굴로 등장한다. 두 개의 키워드, 그리고 "비동기 코드를 마치 위에서 아래로 흐르는 동기 코드처럼 보이게 한다"는 동일한 설명. 파이썬, 러스트, 스위프트가 모두 같은 명분을 내세운다. 브라운대학 연구진의 논문 「A Design Space Exploration of Async/Await」는 이 공유된 문법 뒤에 실제로는 서로 합의되지 않은 아홉 가지 독립적인 설계 결정이 숨어 있으며, 현대의 일곱 개 런타임 중 어느 둘도 가장 단순한 비동기 프로그램조차 같은 방식으로 실행하지 않는다는 사실을 보여준다.

연구진은 이벤트 루프나 콜백이 아니라 일직선으로 읽히는 코드를 지향하는 이 방식을 '직선형 비동기(straight-line asynchrony)'라고 부른다. 이 아이디어는 15년 넘게 여러 언어에서 다듬어져 왔지만, 정작 언어들끼리 얼마나 비슷한지를 파고들자 예상보다 훨씬 크게 갈라져 있었다는 것이 논문의 출발점이다.

로그 한 줄이 네 가지 답을 낳는다

논문이 제시한 예제는 놀랄 만큼 단순하다. 한 함수는 로그를 기록하고, 다른 함수는 그 로그 기록을 백그라운드 작업으로 던져 놓은 뒤 곧바로 다음으로 넘어간다. 하는 일이라곤 로그 한 줄을 배경에서 남기는 것뿐이다. 그런데 일곱 개 런타임에 이 프로그램을 돌리면 결과가 네 갈래로 갈린다. 더 나아가 이 프로그램의 세 가지 변형을 놓고 보면, 일곱 런타임 중 같은 출력을 내는 조합이 단 하나도 없다. '정답'이 따로 없는 것이다. 당신이 머릿속에 그린 결과는 어떤 언어에서는 맞고 다른 언어에서는 틀리다.

아홉 개의 설계 축

프로그래밍 인플루언서의 영상에서 한 번쯤 들어봤을 '핫 스타트'와 '콜드 스타트'가 대표적인 사례다. 핫 스타트는 함수를 부르는 순간 이미 실행 중인 작업을 돌려주고, 콜드 스타트는 await로 깨우기 전까지 아무것도 하지 않는 정지된 객체를 돌려준다. 연구진은 이처럼 성능이 아니라 프로그램의 관찰 가능한 동작 자체를 바꾸는 결정을 '설계 차원(dimension)'이라 부르며, 이 핫/콜드 구분에는 '적극성(Eagerness)'이라는 이름을 붙였다.

논문은 이런 축을 모두 아홉 개로 정리하고, 작업의 생애주기에 따라 세 묶음으로 나눈다. 작업이 어떻게 시작되는지(Start of Life), 어떻게 끝나는지(End of Life), 그리고 취소는 어떻게 다루는지(Cancellation)다. 여기에는 await 지점이 실제로 실행을 중단하는지에 대한 보장, 런타임이 작업을 붙잡는 참조의 종류, 처리되지 않은 작업에서 발생한 예외를 어떻게 할지, 취소가 작업 그래프를 따라 어느 방향으로 전파되는지, 취소된 작업이 이를 무시하고 계속 갈 수 있는지 등이 포함된다.

스위프트와 Trio가 갈라지는 이유

예제의 결과 차이를 설명하는 핵심은 두 축이다. 하나는 작업의 '수명 범위(Extent)'다. 자바스크립트·C#·Tokio·Smol·Asyncio는 기본적으로 작업이 런타임이 끝날 때까지 살아남는 무기한 수명을 택했다. 반면 스위프트와 파이썬의 Trio는 작업이 자신을 낳은 함수 범위를 벗어나 살아남을 수 없는 '동적 수명(Dynamic Extent)'을 골랐다. 즉 백그라운드로 던진 로그 작업이 그 함수가 끝나는 순간까지만 존재할 수 있다.

그런데 스위프트와 Trio는 같은 동적 수명을 택하고도 '소멸 방식(Destruction)'에서 갈린다. 함수 범위가 끝날 때 스위프트는 작업을 취소해 버리는 방식이라 로그 작업이 완료되지 못하고, Trio는 작업이 끝나기를 정중히 기다려 준다. 그래서 같은 코드가 스위프트에서는 "AC"를, Trio에서는 "ABC"를 출력한다. 어느 쪽도 버그가 아니라, 각 언어가 성능·메모리·사용성·의미론 사이에서 내린 서로 다른 합리적 선택의 결과다.

연구진은 이 설계 공간을 말로만 서술하는 데 그치지 않고 비동기 프로그램의 핵심 계산 모델 위에 형식 의미론으로 옮겼다. 이 모델에서 예제의 실행은 추상 기계 상태들을 잇는 작은 단계의 환원으로 추적되며, 대부분의 규칙은 어느 런타임에서나 똑같이 동작하고 오직 설계 결정에 해당하는 소수의 규칙만이 결과를 가르는 '갈림길'로 강조된다.

실무자에게 이 논문의 쓸모는 특정 언어를 추켜세우거나 깎아내리는 데 있지 않다. 오히려 "내가 쓰는 언어의 async는 정확히 어떤 선택을 했는가"를 아홉 개의 축으로 점검하게 만든다는 점이다. 백그라운드 작업을 fire-and-forget으로 던졌을 때 그 작업이 함수 스코프를 넘어 살아남는지, 처리되지 않은 예외가 조용히 삼켜지는지, 취소 신호가 어느 방향으로 흐르는지는 모두 이 축들 위에서 결정된다. 다만 논문 자체는 설계 공간을 지도로 그리는 데 초점이 있어, 각 선택이 실제 서비스 코드에서 어떤 장애로 이어지는지에 대한 처방까지 제공하지는 않는다. 그 간극을 메우는 일은 결국 자신이 쓰는 런타임의 기본값을 직접 확인하는 개발자의 몫으로 남는다.

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

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

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

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