Rust Async/Await ์™„๋ฒฝ ๊ฐ€์ด๋“œ: Tokio, Futures, ๋น„๋™๊ธฐ ๋™์‹œ์„ฑ ์‹ฌ์ธต ๋ถ„์„

Rust์˜ Async/Await๋ฅผ Tokio ๋Ÿฐํƒ€์ž„, Future ํŠธ๋ ˆ์ดํŠธ, ํƒœ์Šคํฌ ์Šคํฐ, ๊ตฌ์กฐํ™”๋œ ๋™์‹œ์„ฑ, ์‹ค์ „ ํŒจํ„ด๊นŒ์ง€ ๊นŠ์ด ์žˆ๊ฒŒ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.

Rust async await concurrency with Tokio runtime and futures execution flow

Rust๋Š” ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ „์„ฑ๊ณผ ์„ฑ๋Šฅ์„ ๋™์‹œ์— ์ œ๊ณตํ•˜๋Š” ์‹œ์Šคํ…œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋กœ, ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ๋„ ๋…ํŠนํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์ทจํ•ฉ๋‹ˆ๋‹ค. JavaScript์˜ Promise๋‚˜ Python์˜ asyncio์™€ ๋‹ฌ๋ฆฌ, Rust์˜ async/await๋Š” ์ œ๋กœ ์ฝ”์ŠคํŠธ ์ถ”์ƒํ™” ์›์น™์„ ๋”ฐ๋ฅด๋ฉฐ, ๋Ÿฐํƒ€์ž„ ์˜ค๋ฒ„ํ—ค๋“œ ์—†์ด ๊ณ ์„ฑ๋Šฅ ๋น„๋™๊ธฐ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณธ ๊ธ€์—์„œ๋Š” Rust์˜ Future ํŠธ๋ ˆ์ดํŠธ๋ถ€ํ„ฐ Tokio ๋Ÿฐํƒ€์ž„, ๊ทธ๋ฆฌ๊ณ  ์‹ค์ „ ๋™์‹œ์„ฑ ํŒจํ„ด๊นŒ์ง€ ์‹ฌ์ธต์ ์œผ๋กœ ์‚ดํŽด๋ด…๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ๊ฐœ๋…: Rust์˜ Future๋Š” ๊ฒŒ์œผ๋ฅธ(lazy) ์‹คํ–‰ ๋ชจ๋ธ์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค

Rust์˜ Future๋Š” ์ƒ์„ฑ ์‹œ์ ์— ์‹คํ–‰๋˜์ง€ ์•Š๊ณ , .await๋ฅผ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ ๋Ÿฐํƒ€์ž„์— ์˜ํ•ด poll๋  ๋•Œ๊นŒ์ง€ ์•„๋ฌด ์ž‘์—…๋„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Š” JavaScript์˜ Promise๊ฐ€ ์ƒ์„ฑ ์ฆ‰์‹œ ์‹คํ–‰๋˜๋Š” ๊ฒƒ๊ณผ ๊ทผ๋ณธ์ ์œผ๋กœ ๋‹ค๋ฅธ ๋™์ž‘ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

Rust Future์™€ ๋‹ค๋ฅธ ์–ธ์–ด์˜ ์ฐจ์ด์ 

Rust์˜ ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ Future ํŠธ๋ ˆ์ดํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. ์ด ํŠธ๋ ˆ์ดํŠธ๋Š” ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์ •์˜๋˜์–ด ์žˆ์œผ๋ฉฐ, ๋ชจ๋“  ๋น„๋™๊ธฐ ์ž‘์—…์˜ ๊ทผ๊ฐ„์ด ๋ฉ๋‹ˆ๋‹ค.

rust
// core::future::Future
trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

poll ๋ฉ”์„œ๋“œ๋Š” Future์˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ณ , ์™„๋ฃŒ๋˜์—ˆ์œผ๋ฉด Poll::Ready(value)๋ฅผ, ์•„์ง ์ง„ํ–‰ ์ค‘์ด๋ฉด Poll::Pending์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ค‘์š”ํ•œ ์ ์€ Rust์˜ Future๊ฐ€ pull ๊ธฐ๋ฐ˜ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋Ÿฐํƒ€์ž„์ด Future๋ฅผ ๋ฐ˜๋ณต์ ์œผ๋กœ pollํ•˜์—ฌ ์ง„ํ–‰ ์ƒํ™ฉ์„ ํ™•์ธํ•˜๋ฉฐ, Future๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•˜์„ ๋•Œ๋Š” waker๋ฅผ ๋“ฑ๋กํ•˜์—ฌ ๋‚˜์ค‘์— ๋‹ค์‹œ poll๋  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

JavaScript๋‚˜ Python์˜ ๋น„๋™๊ธฐ ๋ชจ๋ธ๊ณผ ๋น„๊ตํ–ˆ์„ ๋•Œ ์ฃผ์š” ์ฐจ์ด์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ฒซ์งธ, Rust Future๋Š” ํž™ ํ• ๋‹น ์—†์ด ์Šคํƒ์— ๋ฐฐ์น˜๋  ์ˆ˜ ์žˆ์–ด ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. ๋‘˜์งธ, ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ async ํ•จ์ˆ˜๋ฅผ ์ƒํƒœ ๋จธ์‹ ์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋Ÿฐํƒ€์ž„ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ตœ์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค. ์…‹์งธ, ๋Ÿฐํƒ€์ž„์ด ์–ธ์–ด์— ๋‚ด์žฅ๋˜์–ด ์žˆ์ง€ ์•Š์•„ Tokio, async-std ๋“ฑ ์—ฌ๋Ÿฌ ๋Ÿฐํƒ€์ž„ ์ค‘ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Tokio ๋Ÿฐํƒ€์ž„ ์„ค์ •

Tokio๋Š” Rust ์ƒํƒœ๊ณ„์—์„œ ๊ฐ€์žฅ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ๋น„๋™๊ธฐ ๋Ÿฐํƒ€์ž„์ž…๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ๊ฒ€์ฆ๋œ ์•ˆ์ •์„ฑ๊ณผ ํ’๋ถ€ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋ฉฐ, ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ์ž‘์—… ์Šค์ผ€์ค„๋ง, ํƒ€์ด๋จธ, I/O ๋“œ๋ผ์ด๋ฒ„ ๋“ฑ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

toml
# Cargo.toml
[dependencies]
tokio = { version = "2", features = ["rt-multi-thread", "macros", "net", "time"] }

rt-multi-thread ํ”ผ์ฒ˜๋Š” ์—ฌ๋Ÿฌ OS ์Šค๋ ˆ๋“œ์—์„œ ํƒœ์Šคํฌ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์ž‘์—… ์Šคํ‹ธ๋ง ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. macros ํ”ผ์ฒ˜๋Š” #[tokio::main]๊ณผ #[tokio::test] ๊ฐ™์€ ํŽธ์˜ ๋งคํฌ๋กœ๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, net๊ณผ time์€ ๊ฐ๊ฐ ๋„คํŠธ์›Œํฌ I/O์™€ ํƒ€์ด๋จธ ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.

main.rsrust
#[tokio::main]
async fn main() {
    let result = fetch_data().await;
    println!("Got: {result}");
}

async fn fetch_data() -> String {
    // Simulate async I/O
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    String::from("data loaded")
}

#[tokio::main] ๋งคํฌ๋กœ๋Š” Tokio ๋Ÿฐํƒ€์ž„์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  async main ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋‚ด๋ถ€์ ์œผ๋กœ ์ด ๋งคํฌ๋กœ๋Š” Runtime::new()๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  block_on์œผ๋กœ Future๋ฅผ ์™„๋ฃŒ๊นŒ์ง€ ์‹คํ–‰ํ•˜๋Š” ์ฝ”๋“œ๋กœ ํ™•์žฅ๋ฉ๋‹ˆ๋‹ค.

ํƒœ์Šคํฌ ์Šคํฐ๊ณผ ๊ตฌ์กฐํ™”๋œ ๋™์‹œ์„ฑ

๋‹จ์ผ Future๋ฅผ ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋Š” ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์ง„์ •ํ•œ ์ด์ ์„ ์–ป์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. tokio::spawn์„ ์‚ฌ์šฉํ•˜๋ฉด ๋…๋ฆฝ์ ์ธ ํƒœ์Šคํฌ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋™์‹œ์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

concurrent_tasks.rsrust
use tokio::task::JoinHandle;

#[tokio::main]
async fn main() {
    // Spawn two independent tasks
    let handle_a: JoinHandle<u32> = tokio::spawn(async {
        expensive_computation("dataset_a").await
    });

    let handle_b: JoinHandle<u32> = tokio::spawn(async {
        expensive_computation("dataset_b").await
    });

    // Await both results
    let (result_a, result_b) = (
        handle_a.await.expect("task A panicked"),
        handle_b.await.expect("task B panicked"),
    );

    println!("Results: {result_a}, {result_b}");
}

async fn expensive_computation(name: &str) -> u32 {
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    println!("{name} done");
    42
}

JoinHandle์€ ์Šคํฐ๋œ ํƒœ์Šคํฌ์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ํ•ธ๋“ค์ž…๋‹ˆ๋‹ค. ํƒœ์Šคํฌ๊ฐ€ ํŒจ๋‹‰ํ•˜๋ฉด JoinError๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ์ ์ ˆํ•œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์Šคํฐ๋œ ํƒœ์Šคํฌ๋Š” Send + 'static ์ œ์•ฝ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค

tokio::spawn์— ์ „๋‹ฌ๋˜๋Š” Future๋Š” Send์™€ 'static ํŠธ๋ ˆ์ดํŠธ ๋ฐ”์šด๋“œ๋ฅผ ๋งŒ์กฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํƒœ์Šคํฌ๊ฐ€ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๊ณ , ์›๋ž˜ ์Šค์ฝ”ํ”„๋ณด๋‹ค ์˜ค๋ž˜ ์‚ด ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋กœ์ปฌ ์ฐธ์กฐ๋ฅผ ์บก์ฒ˜ํ•ด์•ผ ํ•œ๋‹ค๋ฉด tokio::task::spawn_local์ด๋‚˜ ์Šค์ฝ”ํ”„๋“œ ํƒœ์Šคํฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

tokio::join!๊ณผ tokio::select!๋กœ ์—ฌ๋Ÿฌ Future ๊ฒฐํ•ฉํ•˜๊ธฐ

์—ฌ๋Ÿฌ ๋น„๋™๊ธฐ ์ž‘์—…์„ ์กฐํ•ฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋”ฐ๋ผ ํ”„๋กœ๊ทธ๋žจ์˜ ๋™์ž‘์ด ํฌ๊ฒŒ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. tokio::join!์€ ๋ชจ๋“  Future๋ฅผ ๋™์‹œ์— ์‹คํ–‰ํ•˜๊ณ  ์ „๋ถ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.

join_example.rsrust
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    // All three run concurrently, total time ~200ms (not 600ms)
    let (users, orders, inventory) = tokio::join!(
        fetch_users(),
        fetch_orders(),
        fetch_inventory()
    );

    println!("Users: {}, Orders: {}, Stock: {}", users.len(), orders.len(), inventory);
}

async fn fetch_users() -> Vec<String> {
    sleep(Duration::from_millis(200)).await;
    vec!["Alice".into(), "Bob".into()]
}

async fn fetch_orders() -> Vec<String> {
    sleep(Duration::from_millis(150)).await;
    vec!["ORD-001".into()]
}

async fn fetch_inventory() -> u32 {
    sleep(Duration::from_millis(100)).await;
    84
}

์œ„ ์˜ˆ์ œ์—์„œ ์„ธ ๊ฐœ์˜ fetch ํ•จ์ˆ˜๊ฐ€ ๋™์‹œ์— ์‹คํ–‰๋˜์–ด ์ด ์†Œ์š” ์‹œ๊ฐ„์€ ๊ฐ€์žฅ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…์ธ 200ms์— ์ˆ˜๋ ดํ•ฉ๋‹ˆ๋‹ค. ์ˆœ์ฐจ ์‹คํ–‰์ด์—ˆ๋‹ค๋ฉด 450ms๊ฐ€ ์†Œ์š”๋˜์—ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋ฐ˜๋ฉด tokio::select!๋Š” ์—ฌ๋Ÿฌ Future ์ค‘ ๊ฐ€์žฅ ๋จผ์ € ์™„๋ฃŒ๋˜๋Š” ํ•˜๋‚˜๋งŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํƒ€์ž„์•„์›ƒ ๊ตฌํ˜„, ์ทจ์†Œ, ๋˜๋Š” ์—ฌ๋Ÿฌ ์†Œ์Šค ์ค‘ ๊ฐ€์žฅ ๋น ๋ฅธ ์‘๋‹ต์„ ์„ ํƒํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

select_example.rsrust
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    tokio::select! {
        val = fetch_from_cache() => {
            println!("Cache hit: {val}");
        }
        val = fetch_from_database() => {
            println!("DB result: {val}");
        }
    }
}

async fn fetch_from_cache() -> String {
    sleep(Duration::from_millis(5)).await;
    "cached_value".into()
}

async fn fetch_from_database() -> String {
    sleep(Duration::from_millis(50)).await;
    "db_value".into()
}

์บ์‹œ ์กฐํšŒ๊ฐ€ 5ms๋กœ ๋” ๋น ๋ฅด๋ฏ€๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ๋Š” ์ทจ์†Œ๋˜๊ณ  ์บ์‹œ ๊ฐ’์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. select!๋Š” ์„ ํƒ๋˜์ง€ ์•Š์€ ๋ธŒ๋žœ์น˜์˜ Future๋ฅผ ๋“œ๋กญํ•˜๋ฏ€๋กœ ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ๊ฐ€ ์ž๋™์œผ๋กœ ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค.

Rust ๋ฉด์ ‘ ์ค€๋น„๊ฐ€ ๋˜์…จ๋‚˜์š”?

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, flashcards, ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์—ฐ์Šตํ•˜์„ธ์š”.

๋น„๋™๊ธฐ Rust์—์„œ์˜ ์—๋Ÿฌ ์ฒ˜๋ฆฌ

๋น„๋™๊ธฐ ์ฝ”๋“œ์—์„œ์˜ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋Š” ๋™๊ธฐ ์ฝ”๋“œ์™€ ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Result ํƒ€์ž…๊ณผ ? ์—ฐ์‚ฐ์ž๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ปค์Šคํ…€ ์—๋Ÿฌ ํƒ€์ž…์„ ์ •์˜ํ•˜์—ฌ ๋‹ค์–‘ํ•œ ์—๋Ÿฌ ์†Œ์Šค๋ฅผ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

error_handling.rsrust
use std::io;

#[derive(Debug)]
enum AppError {
    Network(reqwest::Error),
    Parse(serde_json::Error),
    Io(io::Error),
}

async fn load_config(url: &str) -> Result<Config, AppError> {
    let response = reqwest::get(url)
        .await
        .map_err(AppError::Network)?;

    let text = response.text()
        .await
        .map_err(AppError::Network)?;

    let config: Config = serde_json::from_str(&text)
        .map_err(AppError::Parse)?;

    Ok(config)
}

#[derive(serde::Deserialize)]
struct Config {
    db_url: String,
    port: u16,
}

์‹ค๋ฌด์—์„œ๋Š” thiserror ํฌ๋ ˆ์ดํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—๋Ÿฌ ํƒ€์ž…์„ ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ •์˜ํ•˜๊ฑฐ๋‚˜, anyhow ํฌ๋ ˆ์ดํŠธ๋กœ ์—๋Ÿฌ๋ฅผ ๋™์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์„ ๋งŽ์ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์—์„œ๋Š” anyhow::Result๊ฐ€ ํŽธ๋ฆฌํ•˜๊ณ , ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฝ”๋“œ์—์„œ๋Š” ๋ช…์‹œ์ ์ธ ์—๋Ÿฌ ํƒ€์ž…์ด ์„ ํ˜ธ๋ฉ๋‹ˆ๋‹ค.

ํƒœ์Šคํฌ ๊ฐ„ ๋น„๋™๊ธฐ ํ†ต์‹  ์ฑ„๋„

๋™์‹œ์— ์‹คํ–‰๋˜๋Š” ํƒœ์Šคํฌ ๊ฐ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ „๋‹ฌํ•˜๋ ค๋ฉด ์ฑ„๋„์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Tokio๋Š” ์—ฌ๋Ÿฌ ์ข…๋ฅ˜์˜ ์ฑ„๋„์„ ์ œ๊ณตํ•˜๋ฉฐ, ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ๊ฒƒ์€ mpsc(๋‹ค์ค‘ ์ƒ์‚ฐ์ž, ๋‹จ์ผ ์†Œ๋น„์ž) ์ฑ„๋„์ž…๋‹ˆ๋‹ค.

channel_example.rsrust
use tokio::sync::mpsc;

#[tokio::main]
async fn main() {
    // Bounded channel with capacity 32
    let (tx, mut rx) = mpsc::channel::<String>(32);

    // Producer task
    let producer = tokio::spawn(async move {
        for i in 0..5 {
            tx.send(format!("message-{i}")).await.unwrap();
            tokio::time::sleep(std::time::Duration::from_millis(10)).await;
        }
        // tx dropped here, closing the channel
    });

    // Consumer reads until channel closes
    while let Some(msg) = rx.recv().await {
        println!("Received: {msg}");
    }

    producer.await.unwrap();
}
๋™์‹œ์„ฑ ํ”„๋ฆฌ๋ฏธํ‹ฐ๋ธŒ ์„ ํƒ ๊ฐ€์ด๋“œ

์ƒํ™ฉ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๋™์‹œ์„ฑ ํ”„๋ฆฌ๋ฏธํ‹ฐ๋ธŒ๋ฅผ ์„ ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ์ƒ์‚ฐ์ž์—์„œ ํ•œ ์†Œ๋น„์ž๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•  ๋•Œ๋Š” mpsc๋ฅผ, ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•˜๋ฉด broadcast๋ฅผ, ํ•œ ๋ฒˆ๋งŒ ๊ฐ’์„ ์ „๋‹ฌํ•œ๋‹ค๋ฉด oneshot์„, ์ตœ์‹  ๊ฐ’๋งŒ ์ค‘์š”ํ•˜๋‹ค๋ฉด watch ์ฑ„๋„์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ณต์œ  ์ƒํƒœ๊ฐ€ ํ•„์š”ํ•˜๋ฉด RwLock์ด๋‚˜ Mutex๋ฅผ ๊ณ ๋ คํ•˜๋˜, ๊ฐ€๋Šฅํ•˜๋ฉด ์ฑ„๋„์„ ํ†ตํ•œ ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ ๋ฐฉ์‹์„ ์„ ํ˜ธํ•ฉ๋‹ˆ๋‹ค.

์‹ค์ „ ํŒจํ„ด: ์†๋„ ์ œํ•œ์ด ์žˆ๋Š” HTTP ์š”์ฒญ

์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ์™ธ๋ถ€ API ํ˜ธ์ถœ ์‹œ ๋™์‹œ ์š”์ฒญ ์ˆ˜๋ฅผ ์ œํ•œํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. Semaphore๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋™์‹œ ์‹คํ–‰ ํƒœ์Šคํฌ ์ˆ˜๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

rate_limited_fetcher.rsrust
use std::sync::Arc;
use tokio::sync::Semaphore;

async fn fetch_all(urls: Vec<String>, max_concurrent: usize) -> Vec<Result<String, String>> {
    let semaphore = Arc::new(Semaphore::new(max_concurrent));
    let mut handles = Vec::new();

    for url in urls {
        let sem = Arc::clone(&semaphore);
        let handle = tokio::spawn(async move {
            // Acquire permit before making request
            let _permit = sem.acquire().await.unwrap();
            reqwest::get(&url)
                .await
                .map(|r| r.status().to_string())
                .map_err(|e| e.to_string())
            // permit dropped here, allowing next task to proceed
        });
        handles.push(handle);
    }

    let mut results = Vec::new();
    for handle in handles {
        results.push(handle.await.unwrap());
    }
    results
}

์„ธ๋งˆํฌ์–ด permit์€ _permit ๋ณ€์ˆ˜๊ฐ€ ์Šค์ฝ”ํ”„๋ฅผ ๋ฒ—์–ด๋‚  ๋•Œ ์ž๋™์œผ๋กœ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค. ์ด ํŒจํ„ด์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ํ’€, ํŒŒ์ผ ๋””์Šคํฌ๋ฆฝํ„ฐ ์ œํ•œ, API ๋ ˆ์ดํŠธ ๋ฆฌ๋ฐ‹ ์ค€์ˆ˜ ๋“ฑ ๋‹ค์–‘ํ•œ ์ƒํ™ฉ์—์„œ ํ™œ์šฉ๋ฉ๋‹ˆ๋‹ค.

Pin๊ณผ Unpin: ๋น„๋™๊ธฐ Rust์— ํ•„์š”ํ•œ ์ด์œ 

Future ํŠธ๋ ˆ์ดํŠธ์˜ poll ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜์—์„œ Pin<&mut Self>๊ฐ€ ๋“ฑ์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ž๊ธฐ ์ฐธ์กฐ(self-referential) ๊ตฌ์กฐ์ฒด์™€ ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๋‹ค. async ๋ธ”๋ก์ด ์ปดํŒŒ์ผ๋Ÿฌ์— ์˜ํ•ด ์ƒํƒœ ๋จธ์‹ ์œผ๋กœ ๋ณ€ํ™˜๋  ๋•Œ, ์ง€์—ญ ๋ณ€์ˆ˜์™€ ๊ทธ์— ๋Œ€ํ•œ ์ฐธ์กฐ๊ฐ€ ๊ฐ™์€ ๊ตฌ์กฐ์ฒด ์•ˆ์— ์ €์žฅ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งŒ์•ฝ ์ด ๊ตฌ์กฐ์ฒด๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ๋‚ด์—์„œ ์ด๋™ํ•˜๋ฉด, ๋‚ด๋ถ€ ์ฐธ์กฐ๊ฐ€ ๋ฌดํšจํ™”๋˜์–ด ์ •์˜๋˜์ง€ ์•Š์€ ๋™์ž‘์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. Pin์€ ๊ฐ’์ด ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ด๋™ํ•˜์ง€ ์•Š์Œ์„ ๋ณด์žฅํ•˜๋Š” ํƒ€์ž… ์‹œ์Šคํ…œ ์ˆ˜์ค€์˜ ์žฅ์น˜์ž…๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ํƒ€์ž…์€ Unpin ํŠธ๋ ˆ์ดํŠธ๋ฅผ ์ž๋™์œผ๋กœ ๊ตฌํ˜„ํ•˜์—ฌ ์ด๋™์ด ์•ˆ์ „ํ•จ์„ ๋‚˜ํƒ€๋‚ด๋ฉฐ, ์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ์—์„œ๋Š” Pin์„ ์ง์ ‘ ๋‹ค๋ฃฐ ํ•„์š”๊ฐ€ ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค.

์ €์ˆ˜์ค€ Future ๊ตฌํ˜„์ด๋‚˜ ์ปค์Šคํ…€ combinator๋ฅผ ์ž‘์„ฑํ•  ๋•Œ Pin์„ ์ดํ•ดํ•ด์•ผ ํ•˜์ง€๋งŒ, ์ผ๋ฐ˜์ ์ธ async/await ์‚ฌ์šฉ์—์„œ๋Š” ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋ชจ๋“  ๊ฒƒ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์„ฑ๋Šฅ ํŠน์„ฑ๊ณผ ๋น„๋™๊ธฐ ์‚ฌ์šฉ ์‹œ์ 

๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ด ํ•ญ์ƒ ์ตœ์„ ์˜ ์„ ํƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. CPU ๋ฐ”์šด๋“œ ์ž‘์—…์—๋Š” ๋น„๋™๊ธฐ๊ฐ€ ์˜คํžˆ๋ ค ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋น„๋™๊ธฐ๋Š” I/O ๋ฐ”์šด๋“œ ์ž‘์—…, ํŠนํžˆ ๋งŽ์€ ๋™์‹œ ์—ฐ๊ฒฐ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋„คํŠธ์›Œํฌ ์„œ๋น„์Šค์—์„œ ์ง„๊ฐ€๋ฅผ ๋ฐœํœ˜ํ•ฉ๋‹ˆ๋‹ค.

spawn_blocking_example.rsrust
#[tokio::main]
async fn main() {
    let hash = tokio::task::spawn_blocking(|| {
        // CPU-intensive work runs on a blocking thread
        compute_hash(b"large dataset")
    })
    .await
    .unwrap();

    println!("Hash: {hash}");
}

fn compute_hash(data: &[u8]) -> String {
    use std::collections::hash_map::DefaultHasher;
    use std::hash::{Hash, Hasher};
    let mut hasher = DefaultHasher::new();
    data.hash(&mut hasher);
    format!("{:x}", hasher.finish())
}

spawn_blocking์€ CPU ์ง‘์•ฝ์ ์ธ ์ž‘์—…์„ ๋ณ„๋„์˜ ์Šค๋ ˆ๋“œ ํ’€์—์„œ ์‹คํ–‰ํ•˜์—ฌ ๋น„๋™๊ธฐ ๋Ÿฐํƒ€์ž„์„ ๋ธ”๋กœํ‚นํ•˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ํŒŒ์ผ ์‹œ์Šคํ…œ ์ž‘์—…, ์•”ํ˜ธํ™” ์—ฐ์‚ฐ, ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๋“ฑ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ๋™๊ธฐ ์ž‘์—…์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

Rust์˜ ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ์ฒ˜์Œ์—๋Š” ๋ณต์žกํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ทธ ์„ค๊ณ„ ์ฒ ํ•™์„ ์ดํ•ดํ•˜๋ฉด ๊ฐ•๋ ฅํ•˜๊ณ  ํšจ์œจ์ ์ธ ๋™์‹œ์„ฑ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ต์‹ฌ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • Future ํŠธ๋ ˆ์ดํŠธ๋Š” Rust ๋น„๋™๊ธฐ์˜ ๊ธฐ๋ฐ˜์ด๋ฉฐ, poll ๊ธฐ๋ฐ˜์˜ ๊ฒŒ์œผ๋ฅธ ์‹คํ–‰ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค
  • Tokio ๋Ÿฐํƒ€์ž„์€ ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ์Šค์ผ€์ค„๋ง, ํƒ€์ด๋จธ, I/O ๋“œ๋ผ์ด๋ฒ„๋ฅผ ์ œ๊ณตํ•˜๋Š” ํ”„๋กœ๋•์…˜ ๋ ˆ๋”” ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค
  • tokio::spawn์œผ๋กœ ๋…๋ฆฝ์ ์ธ ํƒœ์Šคํฌ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , **join!**๊ณผ **select!**๋กœ ์—ฌ๋Ÿฌ Future๋ฅผ ์กฐํ•ฉํ•ฉ๋‹ˆ๋‹ค
  • ์ฑ„๋„์„ ํ†ตํ•ด ํƒœ์Šคํฌ ๊ฐ„ ์•ˆ์ „ํ•œ ํ†ต์‹ ์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ, Semaphore๋กœ ๋™์‹œ์„ฑ์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค
  • CPU ๋ฐ”์šด๋“œ ์ž‘์—…์€ spawn_blocking์„ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋™๊ธฐ ๋Ÿฐํƒ€์ž„์„ ๋ธ”๋กœํ‚นํ•˜์ง€ ์•Š๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • Pin์€ ์ž๊ธฐ ์ฐธ์กฐ ๊ตฌ์กฐ์ฒด์˜ ์•ˆ์ „์„ฑ์„ ๋ณด์žฅํ•˜์ง€๋งŒ, ์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ์—์„œ๋Š” ์ง์ ‘ ๋‹ค๋ฃฐ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค

Rust์˜ ๋น„๋™๊ธฐ ์ƒํƒœ๊ณ„๋Š” ๊ณ„์† ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, Tokio๋ฅผ ๋น„๋กฏํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ๋”์šฑ ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์›Œ์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํƒ€์ž… ์‹œ์Šคํ…œ์˜ ๋„์›€์œผ๋กœ ๋ฐ์ดํ„ฐ ๋ ˆ์ด์Šค์™€ ๊ฐ™์€ ๋™์‹œ์„ฑ ๋ฒ„๊ทธ๋ฅผ ์ปดํŒŒ์ผ ํƒ€์ž„์— ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด Rust ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ๊ฐ€์žฅ ํฐ ์žฅ์ ์ž…๋‹ˆ๋‹ค.

์—ฐ์Šต์„ ์‹œ์ž‘ํ•˜์„ธ์š”!

๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์ง€์‹์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

ํƒœ๊ทธ

#rust
#async
#tokio
#futures
#concurrency

๊ณต์œ 

๊ด€๋ จ ๊ธฐ์‚ฌ

Rust ํŠธ๋ ˆ์ดํŠธ์™€ ์ œ๋„ค๋ฆญ ๊ณ ๊ธ‰ ๊ฐ€์ด๋“œ

Rust 2026 ํŠธ๋ ˆ์ดํŠธ์™€ ์ œ๋„ค๋ฆญ ์™„๋ฒฝ ๊ฐ€์ด๋“œ: ์—…์บ์ŠคํŒ…, AsyncFn, ๊ณ ๊ธ‰ ๋ฉด์ ‘ ๋Œ€๋น„

Rust 2024 Edition์˜ ํŠธ๋ ˆ์ดํŠธ ์˜ค๋ธŒ์ ํŠธ ์—…์บ์ŠคํŒ…, AsyncFn ํŠธ๋ ˆ์ดํŠธ, RPITIT ๋“ฑ ์‹ ๊ธฐ๋Šฅ์„ ์ปดํŒŒ์ผ ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. 2026๋…„ ๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ ์ž์ฃผ ์ถœ์ œ๋˜๋Š” ๊ณ ๊ธ‰ ์งˆ๋ฌธ๋„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

Rust ์›น ํ”„๋ ˆ์ž„์›Œํฌ ๋น„๊ต: Actix Web vs Axum 2026

Rust ์›น ํ”„๋ ˆ์ž„์›Œํฌ ๋น„๊ต: Actix Web vs Axum โ€” 2026๋…„ ๊ฐ€์ด๋“œ์™€ ๋ฉด์ ‘ ๋Œ€๋น„

Actix Web 4.13๊ณผ Axum 0.8์˜ ์•„ํ‚คํ…์ฒ˜, ์„ฑ๋Šฅ, ๋ฏธ๋“ค์›จ์–ด ์„ค๊ณ„๋ฅผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. 2026๋…„ Rust ๋ฐฑ์—”๋“œ ๋ฉด์ ‘์—์„œ ์ž์ฃผ ๋‚˜์˜ค๋Š” ์งˆ๋ฌธ๊ณผ ๋ชจ๋ฒ” ๋‹ต๋ณ€๋„ ํ•จ๊ป˜ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

Rust ์†Œ์œ ๊ถŒ๊ณผ ๋นŒ๋ฆผ ๊ฐœ๋…์„ ์„ค๋ช…ํ•˜๋Š” ๊ธฐ์ˆ  ๊ฐ€์ด๋“œ ์ปค๋ฒ„ ์ด๋ฏธ์ง€

Rust ์†Œ์œ ๊ถŒ๊ณผ ๋นŒ๋ฆผ: ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ „์„ฑ์˜ ํ•ต์‹ฌ ์›๋ฆฌ ์™„๋ฒฝ ํ•ด์„ค

Rust์˜ ์†Œ์œ ๊ถŒ(Ownership), ๋นŒ๋ฆผ(Borrowing), ๋ผ์ดํ”„ํƒ€์ž„(Lifetime) ๊ฐœ๋…์„ ์ฒด๊ณ„์ ์œผ๋กœ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. ๋นŒ๋ฆผ ๊ฒ€์‚ฌ๊ธฐ์˜ ๋™์ž‘ ์›๋ฆฌ์™€ ์‹ค๋ฌด ํŒจํ„ด, ๊ธฐ์ˆ  ๋ฉด์ ‘ ๋Œ€๋น„ ํ•ต์‹ฌ ํฌ์ธํŠธ๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค.