Rust – Chia sẻ dữ liệu trên luồng


Được đăng vào ngày 27/01/2018 | 0 bình luận
Rust – Chia sẻ dữ liệu trên luồng
Đánh giá bài viết

Rust cung cấp một số kiểu dữ liệu với tên gọi là kiểu atomic, được định nghĩa trong module std::sync::atomic có thể hỗ trợ chúng ta làm việc với dữ liệu trong các luồng một cách an toàn. Để có thể chia sẻ dữ liệu giữa các luồng thì chúng ta phải đưa dữ liệu đó vào một trong các kiểu atomic có trong Rust như Arc, Mutex, RwLock, AtomicUSize

Về cơ bản thì các kiểu dữ liệu này sẽ thực hiện một cơ chế khóa, tương tự như cơ chế khóa của hệ điều hành, tức là sẽ chỉ cung cấp tài nguyên cho luồng nào nhắm giữ ổ khóa của tài nguyên đó. Một ổ khóa chỉ có thể được nắm giữ bởi một luồng tại một thời điểm, do đó không thể có khả năng có 2 luồng cùng nhau đọc/ghi dữ liệu trên một tài nguyên tại cùng một thời điểm. Các tài nguyên sẽ bị khóa khi cần thiết. Khi một luồng đang nắm giữ ổ khóa của một tài nguyên và hoàn thành công việc của nó, thì ổ khóa sẽ được gỡ ra khỏi tài nguyên đó và một luồng khác có thể bắt đầu làm việc với tài nguyên đó.

Trong Rust thì chúng ta làm điều này với kiểu Mutex<T> trong module std::sync. Mutex sẽ thực hiện cơ chế khóa vừa được giải thích ở trên, chúng ta chỉ cần tạo một đối tượng Mutex và truyền vào tài nguyên của chúng ta là được, ví dụ:

use std::sync::Mutex;
use std::thread;
fn main() {
    let myData = "phocode.com";
    let myMutexData = Mutex::new(myData);
    thread::spawn(move || {
        let myThreadData = myMutexData.lock().unwrap();
        println!("{}", myThreadData);
    });
}

Để tạo một đối tượng Mutex thì chúng ta chỉ cần gọi hàm Mutex::new() và truyền vào dữ liệu mà chúng ta muốn.

phocode.com

Hàm lock() sẽ “khóa” dữ liệu lại và trả về một tham chiếu tới biến mutex đó, luồng này sẽ trả lại khóa cho mutex khi kết thúc hàm closure của nó trong cặp dấu {}. Trong đoạn code trên, chúng ta tạo một luồng và gọi hàm lock() trong đó, nếu chúng ta tạo thêm một luồng nữa và trong luồng này cũng gọi tới hàm lock() như sau:

let myData = "phocode.com";
let myMutexData = Mutex::new(myData);
thread::spawn(move || {
    let myThreadData = myMutexData.lock().unwrap();
    println!("Thread 1: {}", myThreadData);
});
thread::spawn(move || {
    let myThreadData = myMutexData.lock().unwrap();
    println!("Thread 2: {}", myThreadData);
});

thì Rust sẽ báo lỗi:

captured of moved value: `myMutexData`

Để có thể dùng được dữ liệu giữa các luồng thì chúng ta cần phải bọc kiểu Mutex trong một kiểu dữ liệu atomic khác là kiểu Arc<T>. Ví dụ như sau:

use std::sync::Mutex;
use std::sync::Arc;
use std::thread;

fn main() {
    let myData ="phocode.com";
    let myArcData = Arc::new(Mutex::new(myData)); 

    let myMutex1 = myArcData.clone();
    thread::spawn(move || {
        println!("Thread 1: {}", myMutex1.lock().unwrap());
    });

    let myMutex2 = myArcData.clone();
    thread::spawn(move || {
        println!("Thread 2: {}", myMutex2.lock().unwrap());
    });
    thread::sleep_ms(50);
}

Chúng ta tạo một đối tượng Arc bằng cách gọi Arc::new() và truyền vào đối tượng Mutex, sau đó mỗi lần cần sử dụng tới dữ liệu thì chúng ta gọi hàm clone() từ đối tượng Arc để tạo ra một bản copy của đối tượng Mutex và từ đó có thể lock() dữ liệu như bình thường.

Thread 1: phocode.com
Thread 2: phocode.com

Trên thực tế, sau khi tạo ra bản copy Mutex và gọi lock() thì hàm lock() có thể sẽ trả về lỗi Result<T, E>khi số lượng luồng được tạo ra khá nhiều, chứ không phải lúc nào cũng có thể khóa tất cả các mutex lại được. Nếu lock() hoạt động bình thường thì chúng ta mới có thể gọi unwrap() được, nếu có lỗi thì Rust sẽ phát sinh một panic và chúng ta nên bắt lỗi này. Ví dụ:

use std::sync::Mutex;
use std::sync::Arc;
use std::thread;

fn main() {
    let myData = "phocode.com";
    let myArcData = Arc::new(Mutex::new(myData)); 
    for i in 0..50 {
        let myMutex = myArcData.clone();
        thread::spawn(move || {
            let lockResult = myMutex.lock();
            match lockResult {
                Ok(myData) => {
                    println!("Locking OK, here is the data: {}", myData)
                },
                Err(error) => {
                    println!("Locking failed: {}", error);
                }
            }
        });
    }
}

Trong đoạn code trên chúng ta lưu lại kết quả sau khi gọi hàm lock(), nếu lock() thành công thì chúng ta in ra đoạn text “Locking OK…”, ngược lại thì in ra chuỗi “Locking failed…”.

Được đăng vào ngày 27/01/2018