동시성 프로그래밍을 순차 코드처럼 쉽게 작성하도록 돕는 async/await 패턴이 언어마다 예상치 못한 동작 차이를 보인다는 연구 결과가 발표되었습니다. 브라운 대학교 연구진은 동일한 백그라운드 로그 예제를 Python, Rust, Swift, JavaScript 등 7개 런타임에서 실행했을 때 4가지 다른 출력을 확인했습니다. 이는 단순히 성능 차이가 아니라, 태스크의 시작, 종료, 취소와 관련된 9가지 핵심 설계 차원에서 비롯된 것으로 분석됩니다.
연구는 async/await의 동작을 결정하는 9가지 설계 차원을 제시합니다. 예를 들어, 태스크 시작 방식에서는 호출 시 즉시 실행하는 '즉시성(Eagerness)'과 await 전까지 아무 일도 하지 않는 '지연성(Laziness)'이 있습니다. 태스크 종료 방식에서는 태스크를 생성한 함수 스코프가 끝날 때 취소할지(Swift), 아니면 완료될 때까지 기다릴지(Python+Trio)에 따라 결과가 달라집니다. 이처럼 각 언어는 태스크의 수명, 참조 강도, 예외 전파 방식, 취소 인지 및 전파 방향 등 다양한 설계 선택을 통해 고유한 비동기 실행 의미를 가집니다. 이러한 미묘한 차이 때문에 작은 비동기 프로그램조차도 정확히 이해하려면 여러 규칙을 복합적으로 고려해야 합니다.
이러한 설계 차이는 개발자에게 중요한 시사점을 제공합니다. async/await가 순차 코드처럼 보이지만, 그 내부 실행 의미는 언어마다 크게 다를 수 있다는 점을 인지해야 합니다. 이는 성능, 메모리 사용량, 사용 편의성, 그리고 프로그램의 관찰 가능한 동작에 직접적인 영향을 미칩니다. 특히, await를 빠뜨리거나 태스크의 수명 주기를 잘못 이해하면 예상치 못한 경쟁 상태나 버그로 이어질 수 있습니다. 따라서 개발자는 자신이 사용하는 언어의 async/await 구현이 어떤 설계 선택을 따르는지 정확히 파악하고, 이에 맞춰 코드를 작성하는 것이 필수적입니다. 형식 의미론을 통한 실행 추적은 이러한 복잡한 비동기 동작을 이해하는 데 도움을 줄 수 있습니다.