Step 02: 비동기 프로그래밍 기초
foundations 60 min

비동기 프로그래밍 기초

async/await를 배우고 비동기 코드를 작성합니다.

Execute this step

Run from project root:
cargo run

Step 2: 비동기 프로그래밍 기초

학습 목표

  • async/await 문법 배우기
  • 비동기와 동기의 차이 이해하기
  • tokio로 비동기 코드 실행하기

핵심 개념

1. 동기 vs 비동기

동기 (Synchronous):

1fn fetch_data() -> String {
2    // 네트워크 요청 (블로킹)
3    thread::sleep(Duration::from_secs(2));
4    "데이터".to_string()
5}
6
7fn main() {
8    let data = fetch_data(); // 2초 대기
9    println!("{}", data);
10}

비동기 (Asynchronous):

1async fn fetch_data() -> String {
2    // 네트워크 요청 (논블로킹)
3    tokio::time::sleep(Duration::from_secs(2)).await;
4    "데이터".to_string()
5}
6
7#[tokio::main]
8async fn main() {
9    let data = fetch_data().await; // 다른 작업 가능
10    println!("{}", data);
11}

2. async 함수

async fnFuture를 반환하는 함수입니다.

1// 이 함수는
2async fn hello() -> String {
3    "Hello".to_string()
4}
5
6// 실제로는 이렇게 동작
7fn hello() -> impl Future<Output = String> {
8    // ...
9}

3. .await

.awaitFuture가 완료될 때까지 기다립니다.

1#[tokio::main]
2async fn main() {
3    let future = hello(); // Future 생성 (아직 실행 안 함)
4    let result = future.await; // 실행하고 결과 기다림
5    println!("{}", result);
6}

4. 여러 Future 동시 실행

1use tokio::join;
2
3#[tokio::main]
4async fn main() {
5    let (result1, result2, result3) = join!(
6        async_task_1(),
7        async_task_2(),
8        async_task_3(),
9    );
10}

Library를 비동기로 변환

src/lib.rs에 비동기 메서드 추가:

1use tokio::sync::RwLock;
2use std::sync::Arc;
3
4// 비동기에서 안전한 Library
5pub struct AsyncLibrary {
6    library: Arc<RwLock<Library>>,
7}
8
9impl AsyncLibrary {
10    pub fn new() -> Self {
11        AsyncLibrary {
12            library: Arc::new(RwLock::new(Library::new())),
13        }
14    }
15    
16    pub async fn add_book(&self, title: String, author: String, category: Category) -> u32 {
17        let mut lib = self.library.write().await;
18        lib.add_book(title, author, category)
19    }
20    
21    pub async fn find_book(&self, id: u32) -> Option<Book> {
22        let lib = self.library.read().await;
23        lib.find_book(id).cloned()
24    }
25    
26    pub async fn list_all(&self) -> Vec<Book> {
27        let lib = self.library.read().await;
28        lib.list_all().into_iter().cloned().collect()
29    }
30    
31    pub async fn borrow_book(&self, id: u32, borrower: String, due_date: String) -> Result<(), String> {
32        let mut lib = self.library.write().await;
33        lib.borrow_book(id, borrower, due_date)
34    }
35    
36    pub async fn return_book(&self, id: u32) -> Result<(), String> {
37        let mut lib = self.library.write().await;
38        lib.return_book(id)
39    }
40}

Arc와 RwLock

  • Arc (Atomic Reference Counting): 여러 스레드에서 안전하게 공유
  • RwLock (Read-Write Lock): 여러 읽기 또는 하나의 쓰기
1let lib = Arc::new(RwLock::new(Library::new()));
2
3// 읽기 (여러 개 가능)
4let reader1 = lib.read().await;
5let reader2 = lib.read().await;
6
7// 쓰기 (하나만 가능)
8let writer = lib.write().await;

비동기 예제 코드

src/main.rs 또는 examples/async_demo.rs:

1use book_manager::{AsyncLibrary, Category};
2use tokio::time::{sleep, Duration};
3
4#[tokio::main]
5async fn main() {
6    println!("=== 비동기 도서 관리 시스템 ===\n");
7    
8    let library = AsyncLibrary::new();
9    
10    // 비동기로 책 추가
11    println!("책 추가 중...");
12    let id1 = library.add_book(
13        "러스트 프로그래밍".to_string(),
14        "스티브".to_string(),
15        Category::Science,
16    ).await;
17    println!("책 추가 완료 (ID: {})", id1);
18    
19    let id2 = library.add_book(
20        "해리포터".to_string(),
21        "J.K. 롤링".to_string(),
22        Category::Fiction,
23    ).await;
24    println!("책 추가 완료 (ID: {})", id2);
25    
26    println!();
27    
28    // 동시에 여러 작업 실행
29    println!("동시에 2권 대여 처리...");
30    let (result1, result2) = tokio::join!(
31        library.borrow_book(id1, "김철수".to_string(), "2026-02-20".to_string()),
32        library.borrow_book(id2, "이영희".to_string(), "2026-02-22".to_string()),
33    );
34    
35    match result1 {
36        Ok(_) => println!("✓ 책 {} 대여 완료", id1),
37        Err(e) => println!("✗ 책 {} 대여 실패: {}", id1, e),
38    }
39    
40    match result2 {
41        Ok(_) => println!("✓ 책 {} 대여 완료", id2),
42        Err(e) => println!("✗ 책 {} 대여 실패: {}", id2, e),
43    }
44    
45    println!();
46    
47    // 비동기로 전체 목록 조회
48    println!("전체 도서 목록:");
49    let books = library.list_all().await;
50    for book in books {
51        println!("  {}", book);
52    }
53    
54    println!();
55    
56    // 비동기 딜레이 시뮬레이션
57    println!("3초 후 반납...");
58    sleep(Duration::from_secs(3)).await;
59    
60    library.return_book(id1).await.ok();
61    println!("✓ 책 {} 반납 완료", id1);
62    
63    // 최종 상태
64    println!("\n최종 도서 목록:");
65    let books = library.list_all().await;
66    for book in books {
67        println!("  {}", book);
68    }
69}

비동기 태스크 생성

1use tokio::task;
2
3#[tokio::main]
4async fn main() {
5    let library = AsyncLibrary::new();
6    
7    // 백그라운드 태스크 생성
8    let handle = task::spawn(async move {
9        println!("백그라운드 작업 시작");
10        tokio::time::sleep(Duration::from_secs(2)).await;
11        println!("백그라운드 작업 완료");
12    });
13    
14    println!("메인 작업 계속 진행");
15    
16    // 백그라운드 태스크 완료 대기
17    handle.await.unwrap();
18}

체크리스트

  • [ ] async fn을 정의하고 사용했습니다
  • [ ] .await로 Future를 실행했습니다
  • [ ] tokio::main 매크로를 사용했습니다
  • [ ] Arc와 RwLock으로 공유 상태를 관리했습니다
  • [ ] tokio::join!으로 동시 실행했습니다

다음 단계

Step 3에서는 Actix-web으로 REST API 서버를 만듭니다!

Did you find this helpful? Give it a cheer!