기존의 동기식 프로그래밍 모델을 사용하면 시스템이 후속 작업을 진행하기 전에 느린 작업을 완료해야 하는 제약으로 인해 성능 병목 현상이 자주 발생할 수 있으며, 이로 인해 최적의 리소스 할당과 응답이 없는 사용자 인터페이스가 발생할 수 있습니다.

동기식 프로그래밍과 비동기식 프로그래밍은 프로그램 내에서 코드 실행을 관리하는 방식이 다릅니다. 동기식 프로그래밍은 사용자 입력이나 네트워크 요청 완료와 같은 이벤트가 발생할 때까지 모든 스레드가 차단되는 이벤트 중심 모델을 따릅니다. 반면 비동기 프로그래밍은 이벤트 발생을 기다리는 동안 코드가 계속 실행될 수 있도록 콜백이나 약속을 사용합니다. 이를 통해 여러 작업을 동시에 수행할 수 있으므로 시스템 리소스를 효율적으로 사용할 수 있습니다. 비동기 기법의 구현은 프로그램 진행에 지장을 주지 않으면서 수많은 네트워크 요청을 처리하거나 방대한 데이터 세트를 처리할 때 특히 유용합니다.

Rust의 비동기 프로그래밍

Rust에서 사용하는 비동기 프로그래밍 패러다임을 사용하면 기본 실행 스트림을 방해하지 않고 병렬로 작동하는 고효율 코드를 생성할 수 있습니다. 이러한 유형의 프로그래밍은 입출력 프로세스, 네트워크 요청, 외부 리소스를 사용해야 하는 작업을 처리할 때 특히 유용합니다.

앞서 언급한 Rust 애플리케이션 내에서 비동기 프로그래밍을 구현하는 방법에는 언어 기능, 라이브러리 및 Tokio 런타임이 포함됩니다.

Rust의 소유권 모델과 채널 및 잠금과 같은 동시성 프리미티브는 안전하고 효율적인 동시 프로그래밍을 구현할 수 있는 수단을 제공합니다. 이러한 기능은 비동기 프로그래밍을 통해 잘 확장되고 여러 CPU 코어를 사용할 수 있는 동시 시스템을 구축하기 위해 활용할 수 있습니다.

Rust의 비동기 프로그래밍 개념

미래는 아직 완전히 수행되지 않은 계산 프로세스를 포괄하는 Rust의 비동기 프로그래밍의 기초가 됩니다.

퓨처는 비동기 연산을 나타내며 poll() 메서드를 호출할 때만 실행될 수 있습니다. 이 메서드를 호출하면 시스템은 퓨처에 추가 처리가 필요한지 아니면 이미 완료되었는지 여부를 판단합니다. 전자의 경우, 메서드는 Poll::Pending을 반환하여 작업을 나중으로 연기해야 한다는 신호를 보냅니다. 반대로 미래를 실행할 준비가 되었다면 메서드는 최종 결과와 함께 Poll::Ready를 반환합니다.

이 글도 확인해 보세요:  GitHub Copilot을 다운로드하고 VS 코드와 함께 사용하는 방법

Rust의 표준 툴체인은 비동기 입출력 프리미티브, 파일 입출력의 비동기 변형, 네트워크 프로그래밍 및 시간 측정 메커니즘으로 구성됩니다. 이러한 빌딩 블록을 사용하면 입출력 작업을 차단되지 않은 방식으로 실행할 수 있으므로 입출력 작업 완료로 인해 프로그램 작동이 중단되는 것을 방지할 수 있습니다.

비동기 프로그래밍에 `async` 및 `await` 키워드를 사용하면 동기식 코드와 유사한 코드를 생성할 수 있으므로 해당 코드의 이해 및 유지 관리가 용이해집니다.

Rust의 비동기성 설계 철학은 안전과 효율성을 모두 보장하는 데 중점을 두고 있습니다. 이는 메모리 안전을 보장하고 일반적인 동시 프로그래밍의 함정을 피하는 소유권 및 차용 규칙을 통해 달성됩니다. 비동기/대기 구문과 퓨처는 비동기 프로세스를 간단하게 표현할 수 있는 수단을 제공합니다. 또한 타사 런타임을 활용하여 최적화된 실행을 위해 작업을 감독할 수도 있습니다.

이러한 언어 리소스, 라이브러리 및 런타임 환경을 활용하면 매우 효율적인 코드를 생성할 수 있습니다. 이 포괄적이고 사용자 친화적인 프레임워크는 비동기 시스템을 쉽게 구축할 수 있도록 지원하므로, I/O 집약적인 작업의 효율적인 관리와 높은 수준의 동시성이 요구되는 프로젝트에 Rust가 이상적인 선택이 될 수 있습니다.

버전 1.39부터 최신 버전의 Rust는 더 이상 표준 라이브러리 내에서 비동기 연산을 지원하지 않습니다. 비동기 작업을 관리하기 위해 async/await 구문을 활용하려면 Tokio 또는 async-std와 같은 타사 라이브러리를 사용해야 합니다.

Tokio를 사용한 비동기 프로그래밍

Tokio는 프로그래밍 언어 Rust의 고급 비동기 런타임으로, 비동기 기능을 통해 뛰어난 성능과 확장성을 제공합니다. 개발자는 Tokio의 기능을 활용하여 비동기 프로그래밍의 잠재력을 최대한 활용하는 고성능의 확장 가능한 애플리케이션을 구축할 수 있습니다.

Tokio의 근본적인 측면은 비동기 작업 스케줄링 및 실행 메커니즘에 있습니다. 비동기/대기 구문을 사용하면 시스템 리소스 활용과 동시 작업 실행 측면에서 매우 효율적인 비동기 코드를 생성할 수 있습니다. Tokio 내의 이벤트 루프는 작업 스케줄링을 효과적으로 관리하여 CPU 코어 사용을 최적화하고 컨텍스트 전환과 관련된 오버헤드를 최소화합니다.

Tokio의 콤비네이터를 활용하면 작업을 쉽게 조정하고 구성할 수 있습니다.고급 작업 조정 및 구성 도구를 제공하여 ‘조인’을 사용하여 여러 작업의 완료를 기다리거나, ‘선택’을 사용하여 처음 완료된 작업을 선택하거나, ‘레이스’를 통해 서로 작업을 경쟁할 수 있습니다.

이 글도 확인해 보세요:  판다와 폴라: 성능 대결

제공된 지침에 따라 Cargo.toml 파일의 ‘의존성’ 섹션에 Tokio 상자에 필요한 의존성을 추가하세요.

 [dependencies]
tokio = { version = "1.9", features = ["full"] }

다음은 Tokio 라이브러리를 사용하여 비동기 및 대기 기능을 Rust 프로그램에 통합하는 방법에 대한 설명입니다:

 use tokio::time::sleep;
use std::time::Duration;

async fn hello_world() {
    println!("Hello, ");
    sleep(Duration::from_secs(1)).await;
    println!("World!");
}

#[tokio::main]
async fn main() {
    hello_world().await;
}

`hello_world` 함수는 비동기 연산이며 `await` 키워드를 사용하여 미래가 해결될 때까지 진행을 중지합니다. 이 특정 함수는 콘솔에 “Hello,”를 출력한 후 `Duration::from_secs(1)` 함수 호출을 통해 1초 동안 대기합니다. 이 대기 시간이 완료되면 `await` 키워드는 `hello_world` 함수의 실행을 계속하여 궁극적으로 콘솔에 “World!”를 출력합니다.

이 코드 조각의 주요 목적은 “#[tokio::main]” 속성으로 주석이 달린 “hello\_world”라는 동기식 함수를 정의하여 Tokio 런타임의 진입점 역할을 하는 것입니다. 이 함수는 “hello\_world().await”를 호출하여 비동기 방식으로 실행할 수 있습니다.

Tokio로 작업 지연하기

비동기 프로그래밍에서 흔히 사용되는 방법 중 하나는 지연을 활용하거나 지정된 시간 프레임 내에 실행되도록 작업을 할당하는 것입니다. Tokio 런타임은 Tokio::Time 모듈을 통해 비동기 타이머와 지연을 구현할 수 있는 수단을 제공합니다.

Tokio 런타임을 사용하여 프로시저를 연기하려면 다음 단계를 따르세요:

 use std::time::Duration;
use tokio::time::sleep;

async fn delayed_operation() {
    println!("Performing delayed operation...");
    sleep(Duration::from_secs(2)).await;
    println!("Delayed operation completed.");
}

#[tokio::main]
async fn main() {
    println!("Starting...");
    delayed_operation().await;
    println!("Finished.");
}

지연된\_작업 함수는 슬립 메서드를 사용하여 2초의 시간 간격을 도입하며, 이 시간 동안 작업이 일시 중단됩니다. 비동기 구조체로서 지연된\_operation 함수는 할당된 지연이 완전히 제공될 때까지 비작동 상태를 유지하는 대기 메커니즘을 활용할 수 있습니다.

비동기 프로그램에서의 오류 처리

비동기 Rust 프로그램에서 오류를 처리하려면 함수의 결과와 실행 중에 발생할 수 있는 잠재적 오류를 캡처할 수 있는 결과 유형을 활용해야 합니다. 연산자는 이러한 오류를 전파하는 데 사용되므로 프로그램 내에서 오류를 원활하게 처리하고 복구할 수 있습니다.

 use tokio::fs::File;
use tokio::io;
use tokio::io::{AsyncReadExt};

async fn read_file_contents() -> io::Result<String> {
    let mut file = File::open("file.txt").await?;
    let mut contents = String::new();
    file.read_to_string(&mut contents).await?;
    Ok(contents)
}

async fn process_file() -> io::Result<()> {
    let contents = read_file_contents().await?;
    // Process the file contents
    Ok(())
}

#[tokio::main]
async fn main() {
    match process_file().await {
        Ok(()) => println!("File processed successfully."),
        Err(err) => eprintln!("Error processing file: {}", err),
    }
}

`read_file_contents` 함수는 입력/출력 오류 가능성을 나타내는 결과를 반환합니다. 의 활용은 ?연산자를 호출하면 Tokio 런타임이 호출 스택에 오류를 전파할 수 있습니다.

이 글도 확인해 보세요:  HTTP와 HTTPS: 차이점은 무엇인가요?

주요 함수의 주요 목적은 연산에서 얻은 결과를 처리하는 것으로, 연산 결과에 따라 서면 메시지를 생성하는 match 문을 활용합니다.

HTTP 연산에 비동기 프로그래밍 사용

Reqwest와 같은 많은 유명 크레이트는 비동기 HTTP 연산을 제공하기 위해 Tokio의 서비스를 사용합니다.

Python의 비동기 I/O용 라이브러리인 Tokio는 진행 중인 다른 프로세스를 방해하지 않고 여러 HTTP 요청을 수행하기 위해 Reqwest와 함께 활용될 수 있습니다. 이 라이브러리는 시스템 리소스를 효율적으로 관리하면서 동시에 많은 수의 동시 연결을 처리하는 데 적합합니다.

By 박준영

업계에서 7년간 경력을 쌓은 숙련된 iOS 개발자인 박준영님은 원활하고 매끄러운 사용자 경험을 만드는 데 전념하고 있습니다. 애플(Apple) 생태계에 능숙한 준영님은 획기적인 솔루션을 통해 지속적으로 기술 혁신의 한계를 뛰어넘고 있습니다. 소프트웨어 엔지니어링에 대한 탄탄한 지식과 세심한 접근 방식은 독자에게 실용적이면서도 세련된 콘텐츠를 제공하는 데 기여합니다.