0
0
Lập trình
NM

Lập trình bất đồng bộ với Rust tại RustConf 2025

Đăng vào 5 ngày trước

• 6 phút đọc

Giới thiệu

Trong năm nay, tôi đã có dịp tham dự RustConf 2025, một sự kiện tuyệt vời dành cho cộng đồng Rust. Đây là lần đầu tiên tôi tham gia và thật tuyệt khi được gặp gỡ các Rustaceans và học hỏi những kỹ năng mới. Hội nghị diễn ra tại Seattle, nơi tôi đang sinh sống, nên việc tham gia thật dễ dàng! 🥳

Ngày đầu tiên của sự kiện tập trung vào các buổi hội thảo về lập trình bất đồng bộ trong Rust. Tôi đã tham gia buổi hội thảo Rust Async Fundamentals do Herbert Wolverson dẫn dắt, nơi tôi đã có cơ hội tìm hiểu cách xây dựng phần mềm bất đồng bộ trong Rust.

Hội thảo - Cơ bản về Bất đồng bộ

Hội thảo này rất tuyệt vời và Herbert là một người hướng dẫn xuất sắc. Điều tôi thích nhất là cách ông ấy giải thích tokio và các macro khác. Ông đã cho chúng tôi thấy những gì thực sự xảy ra phía sau khi thêm #[tokio::main] trên hàm main(). Điều này giúp tôi nhận ra Rust thực sự làm rất nhiều việc nặng nhọc, trong khi vẫn cung cấp cho bạn toàn quyền kiểm soát!

Hội thảo này là công khai nếu bạn muốn thử sức mình. Hãy theo dõi Herbert và khám phá các khóa học còn lại của ông ấy. Bạn có thể tìm thấy hội thảo trên GitHub.

Những gì tôi đã học hôm nay

Một trong những điều thú vị tôi học được hôm nay là Mô hình Actor. Đây là một mẫu trong lập trình phần mềm khi xây dựng các chương trình đồng thời. Mô hình này tổ chức mã nguồn xung quanh các actor độc lập, cho phép chúng giao tiếp với nhau thông qua việc gửi tin nhắn (sử dụng các thứ như mpsc). Toàn bộ framework actix là một framework Actor cho Rust (mà tôi sử dụng nhiều nhất là actix-web).

Mô hình này tích hợp liền mạch với các nguyên tắc sở hữu và đồng thời của Rust. Vì Rust không có Garbage Collector, các actor cung cấp một cách tinh tế để quản lý an toàn bộ nhớ. Bởi vì các actor giao tiếp thông qua các kênh chia sẻ, chúng không chia sẻ trạng thái có thể thay đổi, điều này giúp bạn tránh được các lỗi dữ liệu và các lỗi đồng thời. 🔥

Ví dụ mã nguồn

Dưới đây là một ví dụ đơn giản về cách tạo một Actor trong Rust:

rust Copy
// Danh sách các lệnh có thể gửi tới Actor.
pub enum SharedStateCommand {
    Increment,
    Get(tokio::sync::oneshot::Sender<u64>), // oneshot là chìa khóa vì đây là cách thông tin được gửi lại - thông qua kênh này
}

// Hàm khởi động Actor. Actor này duy trì trạng thái `counter`. Cách nó xử lý các lệnh là thông qua `Sender` mà nó trả về.
pub async fn start() -> tokio::sync::mpsc::Sender<SharedStateCommand> {

    // Tạo các kênh tx và rx với dung lượng bộ đệm 32 tin nhắn
    let (tx, mut rx) = tokio::sync::mpsc::channel(32);

    // Khởi tạo biến đếm
    let mut counter: u64 = 0;

    // Tạo Actor và di chuyển mọi thứ vào bên trong nó
    tokio::spawn(async move {

        // Xử lý các lệnh được gửi tới nó
        while let Some(cmd) = rx.recv().await {
            match cmd {
                SharedStateCommand::Increment => {
                    counter += 1;
                },
                SharedStateCommand::Get(resp_tx) => {
                    let _ = resp_tx.send(counter);
                }
            }
        }
    });

    tx
}

// Hàm lấy giá trị biến đếm hiện tại bằng cách sử dụng `oneshot` để gửi một lệnh và chờ nhận kết quả qua cùng một kênh
pub async fn get_counter(sender: &tokio::sync::mpsc::Sender<SharedStateCommand>) -> u64 {
    let (resp_tx, resp_rx) = tokio::sync::oneshot::channel();
    let cmd = SharedStateCommand::Get(resp_tx);
    if sender.send(cmd).await.is_err() {
        return 0;
    }
    resp_rx.await.unwrap_or(0)
}

// Hàm này chỉ đơn giản gửi lệnh tới Actor để tăng biến đếm mà không cần chờ kết quả
pub async fn increment(sender: &tokio::sync::mpsc::Sender<SharedStateCommand>) {
    let cmd = SharedStateCommand::Increment;
    let _ = sender.send(cmd).await; // chúng ta không thực sự quan tâm ở đây
}

Sử dụng thư viện

Dưới đây là ví dụ sử dụng thư viện này với framework axum:

rust Copy
use axum::{http::StatusCode, Extension, Json, Router};
use shared_state_actor::SharedStateCommand;
use tokio::sync::mpsc::Sender;

#[tokio::main]
async fn main() {
    // Khởi động Actor và nhận handle của nó - Tôi đã nhập thư viện này trong Cargo.toml
    let my_actor = shared_state_actor::start().await;

    // Một số boilerplate của Axum cũng như các route
    let app = Router::new()
        .route("/", axum::routing::get(|| async { "Hello, World!" }))
        .route("/json", axum::routing::get(hello_json))
        .layer(Extension(my_actor)); // ĐIỀU NÀY RẤT QUAN TRỌNG - Thêm Actor ở đây, để nó có sẵn cho tất cả các route

    // Khởi động server axum
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3001")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();
}

#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct HelloJson {
    message: String,
}

async fn hello_json(
    Extension(my_actor): Extension<Sender<SharedStateCommand>>, // Trích xuất layer ở đây bằng extractor của Axum
) -> axum::Json<HelloJson> {
    // Tăng biến đếm trong actor
    shared_state_actor::increment(&my_actor).await;

    // Lấy giá trị biến đếm hiện tại
    let new_total = shared_state_actor::get_counter(&my_actor).await;

    // Phản hồi lại
    let reply = HelloJson {
        message: format!("Counter: {}", new_total),
    };
    axum::Json(reply)
}

Mẹo hiệu suất

  • Sử dụng kênh tối ưu: Đảm bảo bạn sử dụng các kênh với dung lượng thích hợp để giảm thiểu độ trễ.
  • Quản lý trạng thái: Tránh chia sẻ trạng thái có thể thay đổi giữa các actor để giảm thiểu lỗi đồng thời.
  • Kiểm tra và tối ưu mã: Theo dõi hiệu suất mã và tối ưu khi cần thiết để cải thiện khả năng mở rộng.

Thực tiễn tốt nhất

  • Tách biệt logic: Tạo các actor riêng biệt cho từng phần của ứng dụng để dễ quản lý và mở rộng.
  • Sử dụng tài liệu: Đảm bảo ghi chú rõ ràng và tài liệu cho các phần mã để đồng đội có thể hiểu và sử dụng lại dễ dàng.

Những cạm bẫy thường gặp

  • Quá tải actor: Cần đảm bảo rằng không có actor nào bị quá tải với tác vụ, dẫn đến giảm hiệu suất.
  • Quản lý lỗi: Thêm xử lý lỗi cho các kênh để đảm bảo hệ thống hoạt động trơn tru ngay cả khi có lỗi phát sinh.

Kết luận

Tổng quan về ngày đầu tiên - Tôi thật sự thích nó! Nội dung rất kỹ thuật và đã giúp tôi hiểu rõ hơn về các mẫu bất đồng bộ trong Rust. Tôi rất mong chờ ngày thứ hai khi tôi có thể tham gia các phiên ngắn hơn và giao lưu với nhiều người trong cộng đồng Rust hơn!

Hãy theo dõi phần 2 để biết thêm thông tin chi tiết!

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào