comnic's Dev&Life

[Rust] 14. 비동기 프로그래밍과 async/await 본문

Rust

[Rust] 14. 비동기 프로그래밍과 async/await

comnic 2023. 12. 9. 18:14
반응형

14. 비동기 프로그래밍과 async/await

Rust는 비동기 프로그래밍을 지원하기 위해 async/await 문법을 도입했습니다. 비동기 프로그래밍은 I/O 작업이나 이벤트 처리와 같은 작업에서 유용하며, 애플리케이션의 성능과 응답성을 향상시키는 데에 활용됩니다.

14. 1 async/await 문법

async / await는 아래 예제와 같이 사용할 수 있습니다.

// async 함수 정의
async fn fetch_data() -> String {
    // 가상의 비동기 작업을 수행
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
    "Data Fetched!".to_string()
}

// async 함수 호출
async fn main() {
    let result = fetch_data().await;
    println!("{}", result);
}
  • async fn: 비동기 함수를 정의하는 키워드. 비동기 함수는 async 키워드를 사용하여 정의되며, 반환 타입에는 Future 형식이 사용됩니다.
  • await: 비동기 함수 내에서 다른 비동기 함수의 결과를 기다리는데 사용되는 키워드. await는 비동기 컨텍스트 내에서만 사용 가능합니다.
  • tokio::time::sleep: Tokio 런타임을 사용하여 비동기 대기를 수행하는 함수. tokio::time::Duration::from_secs(2)는 2초 동안 대기한다는 의미입니다.

 

14. 2 비동기 런타임과 익스텐더(Extender)

비동기 런타임은 비동기적으로 실행되는 코드를 관리하고 실행하는 런타임 환경입니다. Rust에서는 여러 비동기 런타임이 사용될 수 있지만, 대표적으로 tokioasync-std가 있습니다. 이 런타임들은 비동기 코드를 실행하고 이벤트 루프를 통해 비동기 작업들을 효율적으로 관리합니다.

  • tokio: 많은 Rust 개발자들이 사용하는 비동기 런타임 중 하나로, tokio::main 어트리뷰트를 이용하여 간편하게 비동기 메인 함수를 정의할 수 있습니다. tokio는 많은 유용한 기능과 도구를 제공하며, 다양한 I/O 작업 및 타이머와 같은 비동기 기능을 지원합니다.
  • async-std: Rust의 표준 라이브러리를 기반으로 하는 비동기 런타임으로, async-std::main 함수를 이용하여 비동기 메인 함수를 정의할 수 있습니다. 경량하고 간결한 구현이 특징이며, 표준 라이브러리와의 호환성을 지향합니다.

익스텐더(Extender)는 비동기 런타임에 추가적인 기능을 제공하는 라이브러리나 도구를 의미합니다. 비동기 런타임 자체는 비동기 작업의 실행을 담당하지만, 익스텐더는 이를 보완하고 확장하는 역할을 합니다.

  • tokio의 익스텐더:
    • tokio::spawn: 비동기 태스크를 스폰하여 별도의 태스크로 실행하게 해줍니다.
    • tokio::sync: 다양한 동기화 기능을 비동기 환경에서 사용할 수 있도록 지원합니다. 예를 들어 tokio::sync::mpsc 채널은 다중 생산자, 단일 소비자(MPMC) 채널을 제공합니다.
    • tokio::time: 비동기적인 대기와 타이머를 구현할 수 있도록 합니다.
  • async-std의 익스텐더:
    • async_std::task::spawn: 비동기 태스크를 스폰하여 실행합니다.
    • async_std::channel: 다양한 채널을 제공하여 비동기적인 통신을 지원합니다.
    • async_std::io: 비동기 입출력을 지원합니다.
// tokio를 이용한 비동기 메인 함수
#[tokio::main]
async fn main() {
    // tokio의 익스텐더 사용
    tokio::spawn(async {
        println!("Async Task with tokio");
    }).await.unwrap();

    // async-std를 이용한 비동기 메인 함수
    async_std::task::spawn(async {
        println!("Async Task with async-std");
    }).await;
}
  • #[tokio::main] 어트리뷰트는 main 함수를 비동기 메인 함수로 변환합니다.
  • tokio::spawn과 async_std::task::spawn을 통해 비동기 태스크를 스폰하여 실행할 수 있습니다.

 tokio와 async-std는 각각의 런타임에 특화된 익스텐더를 가지고 있습니다. 프로젝트에 적합한 런타임과 익스텐더를 선택하여 사용해야 합니다. Rust에서는 async/await 문법을 사용하려면 비동기 런타임이 필요하며, 익스텐더를 통해 비동기 작업을 보다 쉽게 처리할 수 있습니다.

예를 하나 더 보겠습니다.

use tokio::time::Duration;
use tokio::task;

async fn async_task() {
    println!("Async Task Started");
    tokio::time::sleep(Duration::from_secs(2)).await;
    println!("Async Task Completed");
}

fn main() {
    let runtime = tokio::runtime::Runtime::new().unwrap();
    runtime.block_on(async {
        let handle = tokio::spawn(async_task());

        // 기타 동기 작업 수행
        println!("Main Task");

        // 비동기 작업 완료 대기
        handle.await.unwrap();
    });
}
  • tokio::spawn: Tokio 런타임에서 비동기 작업을 스폰하는 함수. 이를 통해 비동기 작업을 별도의 스레드 또는 태스크로 실행할 수 있습니다.
  • runtime.block_on: Tokio 런타임의 block_on 함수를 사용하여 비동기 작업이 완료될 때까지 기다립니다.

 

14. 3 비동기 이벤트 핸들링

비동기 이벤트 핸들링은 비동기적인 환경에서 발생하는 이벤트를 효율적으로 처리하는 것을 의미합니다. Rust에서는 주로 비동기 채널을 사용하여 이벤트를 생성하고 소비하며, 이를 통해 각 태스크 간에 안전하게 데이터를 교환하고 비동기 이벤트를 처리합니다.

use tokio::sync::mpsc;
use tokio::time::Duration;
use tokio::task;

async fn produce_data(sender: mpsc::Sender<&'static str>) {
    for i in 1..=5 {
        tokio::time::sleep(Duration::from_secs(1)).await;
        let data = format!("Data {}", i);
        sender.send(&data).await.unwrap();
    }
}

async fn consume_data(receiver: mpsc::Receiver<&'static str>) {
    while let Some(data) = receiver.recv().await {
        println!("Received: {}", data);
    }
}

#[tokio::main]
async fn main() {
    let (tx, rx) = mpsc::channel::<&'static str>(10);

    let producer_task = tokio::spawn(produce_data(tx));
    let consumer_task = tokio::spawn(consume_data(rx));

    tokio::try_join!(producer_task, consumer_task).unwrap();
}
  • tokio::sync::mpsc: Tokio의 다중 생산자, 단일 소비자(MPMC) 채널을 사용하여 비동기적으로 데이터를 생산하고 소비하는 예제입니다.
  • tokio::time::sleep(Duration::from_secs(1)).await: 비동기 대기를 통해 주기적으로 데이터를 생성하는 부분입니다.
  • receiver.recv().await: 채널에서 비동기적으로 데이터를 수신하는 부분입니다.

 

비동기 이벤트 핸들링의 주요 개념:

  • 이벤트 발생 및 생성: 비동기 작업에서 이벤트는 특정 동작이나 상태 변화를 나타내는 것으로, 예를 들어 타이머가 만료되거나 외부에서 데이터가 도착하는 경우 등이 이벤트에 해당합니다.
  • 이벤트 핸들링: 비동기적인 환경에서는 이벤트가 발생할 때마다 해당 이벤트를 처리하는 핸들러가 실행됩니다. 핸들러는 비동기 함수 또는 클로저로 작성되며, 이벤트가 발생하면 해당 핸들러가 실행됩니다.
  • 비동기 채널 및 태스크 스폰: 비동기 채널은 생산자와 소비자 간에 안전한 데이터 전송을 가능하게 하며, 각각의 작업은 별도의 태스크로 스폰되어 독립적으로 실행됩니다.

주의사항:

  • 비동기 프로그래밍에서는 주의 깊은 관리가 필요하며, Rust에서는 안전성과 성능을 겸비한 비동기 코드를 작성하는 것이 중요합니다.
  • async/await 문법을 사용할 때에는 주변의 코드도 비동기적이어야 하므로, 런타임과 익스텐더 라이브러리를 적절히 활용해야 합니다.
  • tokio는 많이 사용되는 비동기 런타임 중 하나이며, 다양한 비동기 작업들을 지원합니다.

 

반응형
Comments