Những điều cần biết về Fearless Concurrency trong Rust

Concurrency là tính năng của một chương trình để chạy nhiều tác vụ đồng thời trên cùng một lõi CPU. Các tác vụ đồng thời chạy và hoàn thành trong thời gian chồng chéo mà không phải theo thứ tự cụ thể, khác tính song song hay parallelism.

Fearless Concurrency trong Rust

Rust nổi bật bởi các tính năng cho hiệu suất cao, hỗ trợ chạy đồng thời an toàn và hiệu quả. Tính đồng thời của Rust dựa trên khái niệm “fearless concurrency”, trong đó ngôn ngữ nhằm mục đích giúp dễ dàng viết mã đồng thời an toàn thông qua quyền sở hữu và hệ thống vay mượn thực thi các quy tắc nghiêm ngặt tại thời điểm biên dịch nhằm ngăn chặn dấu vết dữ liệu & đảm bảo an toàn cho bộ nhớ.

Concurrency trong Rust là gì?

Rust cung cấp một số nguyên hàm đồng thời cho việc viết các chương trình đồng thời, bao gồm thread, chuyển tin nhắn, mutex, kiểu atomic, async/await cho lập trình không đồng bộ.

Tổng quan về các nguyên hàm đồng nhất của Rust:

  1. Threads: Rust cung cấp mô đun std::thread trong thư viện chuẩn của nó để tạo và quản lý các luồng (thread). Bạn có thể tạo thread mới bằng hàm thread::spawn. Hàm này nhận một bao chứa code thực thi. Bạn cũng có thể chạy các luồng song song. Rust cung cấp đồng bộ hóa các nguyên hàm để phối hợp thực hiện chúng. Borrow checker đảm bảo các tài liệu tham khảo không dẫn tới những hoạt động bất ngờ.
  2. Message Passing: Mô hình đồng thời của Rust hỗ trợ chuyển thông báo giữa các luồng. Bạn sẽ sử dụng các kênh được triển khai thông qua mô-đun std::sync::mpsc để truyền tin nhắn. Một kênh bao gồm một bộ phát (Sender) và một bộ thu (Receiver). Chủ đề có thể gửi tin nhắn qua máy phát và nhận chúng qua máy thu. Điều này cung cấp một cách giao tiếp an toàn và đồng bộ giữa các luồng.
  3. Mutex và các kiểu Atomic: Rust cung cấp các nguyên hàm đồng bộ hóa, bao gồm mutex (std::sync::Mutex) và các kiểu atom (std::sync::atomic), để đảm bảo truy cập chia sẻ dữ liệu độc quyền. Mutex cho phép nhiều luồng truy cập dữ liệu đồng thời, trong khi chặn data race. Các kiểu atomic cung cấp những hoạt động atomic trên dữ liệu được chia sẻ, như tăng bộ đếm mà không yêu cầu khóa rõ ràng.
  4. Async/Await Future: Cú pháp async/await của Rust cung cấp tính năng cho việc viết code không đồng bộ mà bạn có thể thực thi đồng thời. Các chương trình không đồng bộ xử lý hiệu quả những tác vụ liên kết với I/O, cho phép các chương trình thực hiện tác vụ khác trong khi chờ đợi hoạt động I/O khác. Cú pháp async/await của Rust dựa trên future, và bạn có thể cung cấp "năng lượng" cho chúng bằng thư viện runtime async-std hoặc tokio.

Cách dùng spawn thread trong Rust

Bạn sẽ dùng mô đun std:thread để sinh thread. Hàm std::thread::spawn cho phép bạn tạo một thread mới sẽ chạy đồng thời với luồng chính hoặc bất kỳ thread hiện có khác trong chương trình.

Sau đây là cách bạn có thể sinh một luồng với hàm std::thread::spawn:

use std::thread;

fn main() {
    // Sinh một thread mới
    let thread_handle = thread::spawn(|| {
        // Code executed in the new thread goes here
        println!("Hello from the new thread!");
    });

    // Đợi thread đã sinh hoàn tất
    thread_handle.join().unwrap();

    // Code đã thực thi trong luồng chính tiếp tục ở đây
    println!("Hello from the main thread!");
}

Hàm main tạo một thread mới với hàm thread::spawn bằng cách chuyển vào một closure chứa code chạy trong luồng. Closure đó in một thông báo cho biết thread mới đang chạy.

Phương thức join trên thread_handle cho phép luồng chính đợi để luồng đã sinh hoàn tất quá trình thực thi. Bằng cách gọi join, hàm này đảm bảo luồng chính đợi luồng được sinh ra hoàn thành trước khi tiếp tục.

Cách dùng tính nhất quán trong Rust

Bạn có thể sinh nhiều luồng và dùng một vòng lặp hay bất kỳ cấu trúc kiểm soát Rust khác để tạo nhiều closure và sinh thread cho mỗi cấu trúc.

use std::thread;

fn main() {
    let num_threads = 5;

    let mut thread_handles = vec![];

    for i in 0..num_threads {
        let thread_handle = thread::spawn(move || {
            println!("Hello from thread {}", i);
        });
        thread_handles.push(thread_handle);
    }

    for handle in thread_handles {
        handle.join().unwrap();
    }

    println!("All threads finished!");
}

For loop sinh 5 thread, mỗi luồng được gắn với một định danh riêng, i với biến loop. Các closure ghi giá trị của I với từ khóa move để tránh vấn đề về quyền sở hữu. Vector thread_handles chứa các thread dùng sau này cho loop join.

Sau khi sinh tất cả các luồng, hàm main lặp lại trên vector thread_handles, gọi join trên mỗi handle và đợi tất cả các luồng thực thi.

Chuyển thông báo qua các kênh

Bạn có thể chuyển thông báo qua các luồng với kênh của chúng. Rust cung cấp tính năng chuyển thông báo trong mô đun std::sync::mpsc. Tại đây, mpsc là viết tắt của "multiple producer, single consumer", nó cho phép giao tiếp giữa nhiều luồng bằng cách gửi và nhận thông báo qua các kênh.

Đây là cách bạn triển khai thông báo qua kênh liên lạc giữa các luồng trong chương trình:

use std::sync::mpsc;
use std::thread;

fn main() {
    // Tạo một kênh
    let (sender, receiver) = mpsc::channel();

    // Spawn một thread
    thread::spawn(move || {
        // Send a message through the channel
        sender.send("Hello from the thread!").unwrap();
    });

    // Nhận thông báo trong thread chính
    let received_message = receiver.recv().unwrap();
    println!("Received message: {}", received_message);
}

Hàm main tạo một kênh với mpsc::channel(), trả về một sender và một receiver. Sender gửi thông báo tới receiver nhận thông báo. Hàm main tiếp tục sinh ra các luồng và chuyển quyền sở hữu của Sender tới thread closure. Bên trong thread closure, hàm sender.send() gửi thông báo qua kênh đó.

Hàm receiver.recv() nhận thông báo bằng cách tạm dừng thực thi cho tới khi luồng đó nhận thông báo. Hàm main in thông báo tới console sau khi hóa đơn thông báo thành công.

Hướng dẫn dùng tính năng trong Rust

Lưu ý rằng gửi thông báo qua kênh này tốn sender. Nếu cần gửi thông báo từ nhiều luồng, bạn có thể nhân bản sender bằng hàm sender.clone().

Thêm vào đó, mô đun mpsc cung cấp phương thức khác như try_recv(), phương thức “không chặn” cố gắng nhận tin nhắn và iter(), phương thức này tạo một trình vòng lặp trên các tin nhắn đã nhận.

Chuyển tin nhắn qua các kênh cung cấp môi trường an toàn và thuận tiện để giao tiếp giữa các luồng, đồng thời, tránh data race và đảm bảo đồng bộ hóa phù hợp.

Rust kết hợp tất cả nhân tố trên để cung cấp một framework lập trình mạnh mẽ, an toàn và nhất quán. Hi vọng bài viết giúp bạn dùng Fearless Concurrency của Rust hiệu quả.

Thứ Sáu, 14/07/2023 16:56
52 👨 208
0 Bình luận
Sắp xếp theo