많은 시스템 언어는 입력/출력 작업을 기본적으로 지원하지만, 종종 동기와 비동기 접근 방식 사이에 명확한 차이를 두지 않습니다. Rust는 I/O 작업에서 안전성과 성능을 보장하기 위해 처음부터 설계되었습니다. 그래서 Rust 표준 라이브러리(std)에서는 동기 I/O만 구현되어 있으며, 인기 있는 비동기 지원은 별도의 외부 라이브러리(예: tokio, async-std)로 제공됩니다.
동기와 비동기 I/O 접근 방식을 혼합하면 코드 유지 보수가 복잡해지고, 안전성과 성능 문제가 발생할 수 있습니다. 이 접근 방식들은 자원, 스레드 및 락을 다루는 모델이 다르기 때문입니다. 예를 들어, 큰 파일을 직접 읽거나 네트워크에서 데이터를 기다리는 것은 실행 스레드를 블로킹하여 애플리케이션 속도를 저하시킬 수 있습니다.
Rust는 명확한 분리를 제공합니다: 표준 라이브러리(std::io) — 안전한 오류 처리 및 자원 소유권에 대한 엄격한 제어가 있는 동기 I/O만을 제공합니다. 비동기 해결책을 위해 외부 라이브러리 및 비동기 런타임이 사용됩니다. 이들은 유사한 오류 및 안전성 의미를 가지는 고유한 유형 및 API를 제공합니다.
동기 파일 읽기 코드 예시:
use std::fs::File; use std::io::{self, Read}; fn main() -> io::Result<()> { let mut file = File::open("foo.txt")?; let mut contents = String::new(); file.read_to_string(&mut contents)?; println!("{}", contents); Ok(()) }
tokio를 통한 비동기 파일 읽기 코드 예시:
use tokio::fs::File; use tokio::io::AsyncReadExt; #[tokio::main] async fn main() -> tokio::io::Result<()> { let mut file = File::open("foo.txt").await?; let mut contents = String::new(); file.read_to_string(&mut contents).await?; println!("{}", contents); Ok(()) }
주요 특징들:
동기 함수와 비동기 함수 간에 std의 File 및 tokio의 File을 직접 혼합할 수 있나요?
아니요. 이들은 API 수준에서 호환되지 않으며, 표준 유형은 비동기 트레이트를 구현하지 않습니다.
비동기 함수에서 동기 I/O를 호출하면 std::thread::spawn 스레드가 블로킹되나요?
네. 비동기 환경에서 동기 I/O를 호출하면 스레드가 블로킹되어 비동기의 이점을 무시하게 됩니다.
런타임 없이 async fn main을 사용할 수 있나요(tokiо 또는 async-std)?
아니요. 비동기 진입점은 특별한 런타임에서 실행되어야 하며, 그렇지 않으면 컴파일러가 async fn main을 사용할 수 없다고 의미합니다.
Rust로 작성된 멀티 스레드 서버에서 비동기 요청 핸들러 내에서 std::io의 동기 읽기를 사용합니다. 이로 인해 부담이 이벤트 루프를 차단하고, 대기 시간이 증가하며, 서버가 최대 부담을 견디지 못합니다.
장점:
단점:
비동기 코드 내에서 모든 파일 및 네트워크 작업을 위해 비동기 유형만 사용하고, 유형을 엄격히 관리하며 Result를 통해 오류를 잡아냅니다.
장점:
단점: