GN⁺: 스레드 대신 async/await를 선택하는 이유
(notgull.net)- Rust 커뮤니티에서 스레드는 async/await 가 할수 있는걸 다 할 수 있고 더 간단한데 왜 async/await를 선택하는가?라는 질문이 자주 보임
- Rust는 저수준 언어로, Coroutine의 복잡성을 숨기지 않음. 이는 프로그래머가 비동기를 고려할 필요도 없이 기본적으로 비동기로 되는하는 Go와 같은 언어와 반대되는 개념
- 똑똑한 프로그래머는 복잡성을 피하려고 노력하는데,
async
/await
는 왜 필요한가?
배경 알아보기
- Rust는 저수준 언어임. 코드는 일반적으로 선형적이며, 한 작업이 끝나면 다른 작업이 실행됨.
- 웹 서버와 같은 동시에 많은 작업을 실행해야 하는 경우, 선형 코드로는 문제가 발생함.
- 초기 웹은 스레딩을 도입하여 이 문제를 해결하려고 시도함.
- 스레드를 사용하여 동시에 여러 클라이언트를 처리할 수 있으나, 프로그래머들은 OS 공간에서 사용자 공간으로 동시성을 가져오고자 함.
타임아웃 문제
- Rust의 가장 큰 장점 중 하나는 조합성(composability) 임.
-
async
/await
는 I/O 바운드 함수에 이 조합성을 적용할 수 있게 함. - 예를 들어, 클라이언트 처리 함수에 타임아웃을 추가하고자 할 때, 두 개의 조합자를 사용하여 구현할 수 있음.
테마틱 스레드
- 스레드를 사용한 예제에서 타임아웃을 구현하는 것은 쉽지 않음.
-
TcpStream
에는set_read_timeout
과set_write_timeout
함수가 있지만, 이를 사용하는 것은 제한적임. - Rust의 조합자를 사용하여 타임아웃을 프로그래밍하는 방법을 제시하지만, 이는
TcpStream
에만 국한되고 추가 시스템 호출이 필요함.
Async 성공 사례
- HTTP 생태계는
async
/await
를 주요 런타임 메커니즘으로 채택함. -
tower
는async
/await
의 강력함을 보여주는 예로, 타임아웃, 속도 제한, 부하 분산 등을 제공함. -
macroquad
는 Rust 게임 엔진으로,async
/await
를 사용하여 엔진을 실행함.
Async의 이미지 개선
-
async
의 이점이 널리 알려지지 않아 일부 사람들이 오해할 수 있음. - Rust 커뮤니티는
async
Rust의 성능 이점을 과대평가하고 의미 있는 이점을 축소하는 경향이 있음. -
async
/await
는 동기 Rust에서 수십 개의 스레드와 채널 없이는 표현할 수 없는 패턴을 간결하게 표현할 수 있는 강력한 프로그래밍 모델로 봐야 함.
GN⁺의 의견
-
async
/await
는 동시성을 처리할 때 코드의 복잡성을 증가시키지만, 동시에 많은 클라이언트를 효율적으로 처리할 수 있는 능력을 제공함. - 이 기사는
async
/await
가 단순히 성능상의 이점을 넘어서 프로그래밍 모델의 강점을 가지고 있음을 강조함. - Rust의
async
/await
는 다양한 I/O 작업에 대한 조합성을 제공하며, 이는 특히 네트워크 서비스나 웹 서버와 같은 분야에서 유용함. - 비판적인 시각에서 볼 때,
async
/await
의 복잡성은 초보 개발자들에게 진입 장벽이 될 수 있으며, 이를 극복하기 위한 교육적 노력이 필요함. - 동일한 기능을 제공하는 다른 프로젝트로는 Node.js의
async
/await
구현이나 Python의asyncio
라이브러리가 있으며, 이들도 비슷한 패러다임을 제공함. -
async
/await
를 도입할 때는 코드의 복잡성과 유지보수성을 고려해야 하며, 동시에 많은 클라이언트를 처리해야 하는 경우에는 이 모델이 큰 이점을 제공함.
Hacker News 의견
-
Async/await와 단일 스레드
- 자바스크립트 모델처럼 단일 스레드에서의 async/await는 단순하고 잘 이해되어 있음.
- 스레드를 사용하면 여러 CPU가 문제를 처리할 수 있고, Rust는 락 관리를 도와줌.
- 다른 우선순위의 스레드를 가질 수 있으며, 계산에 제한이 있는 경우 필요함.
- 멀티 스레드 async/await는 복잡함. 계산에 제한이 있는 섹션에서는 모델이 붕괴될 수 있음.
- Rust에서 멀티 스레드 계산은 잘 작동하지 않음. 문제점으로는:
- Futex 혼잡 붕괴: 일부 저장소 할당자에서 문제가 될 수 있음.
- 불공정한 뮤텍스의 기아: 표준 Mutex와 crossbeam-channel 채널이 불공정함.
-
Async/await 대 스레드
- 비판은 복잡성에 관한 것이 아니라, 선택에 따라 생태계가 분열되고 하나가 열등해지는 것에 대한 것임.
- Rust 생태계는 IO 작업을 하려면 전부 async/await를 사용해야 한다고 결정함.
- Rust가 async/await 이외의 것들을 더 조합 가능한 추상화로 만들었다면 불만이 사라졌을 것임.
-
기사에 대한 문제점
- 웹 서버 예시 하나만 제시되었고, 스레드에 대해 잘못 해결됨.
- 프로그래머들은 OS 스레드가 아닌 개념적, 의미적 스레드를 원함.
- OS 스레드는 비용이 많이 들고, 우리는 저렴한 스레드를 원함.
- 웹 서버 예시에서의 타임아웃 구현 문제점.
-
다루지 않은 순간들
- async/await는 단일 스레드에서 실행되므로 락이나 동기화가 필요 없음.
- async/await에서의 오류 전파는 명확하지 않음.
- 네트워크 I/O에서 백프레셔도 언급되어야 함.
-
취소에 관한 중요한 점
- 미래의 어떤 작업도 쉽게 취소할 수 있음.
- 스레드에서의 취소는 복잡하고, 강제 스레드 중단은 신뢰할 수 없음.
- Rust의 async 모델에서는 모든 futures에 외부에서 타임아웃을 추가할 수 있음.
-
Async/await에 대한 마케팅 같은 캠페인
- async/await는 기술적 실수였으며, 커뮤니티에 큰 비용을 초래함.
- Rust는 여전히 가장 좋은 언어이지만, 이 논쟁이 영원히 이어질까 걱정됨.
-
Async/await 대 파이버
- Rust는 이전에 그린 스레드를 가졌었고 의도적으로 제거됨.
- futures를 언제든지 드롭할 수 있는 능력이 큰 비용을 수반함.
- async/await의 조합성을 칭찬하는 것은 이상함.
-
Rust의 async/await 주요 이점
- 스레드나 동적 메모리가 없는 상황에서도 작동할 수 있음.
- 동시성을 사용하여 코드를 간결하게 작성할 수 있음.
-
Async/await에 대한 오해
- 단일 스레드에서의 동시성 메커니즘이 필요한 이유를 이해하지 못하는 사람들이 있음.
- UI 프로그래밍, GPU와의 통신, 런타임 간 통신 등에 async/await가 유용함.
-
Async/await 대 스레드 선택 이유
- async/await는 클라이언트/요청/작업 상태의 메모리 사용량을 줄일 수 있음.
- 상태 압축은 메모리 속도가 느린 현대에서 성능에 중요함.
- async/await와 CPS는 클라이언트당 메모리 사용량을 줄이는 데 효과적임.