Portal/Notes πŸ“
Rust

Menjalankan Kode Secara Simultan

Versi Bahasa Indonesia. Baca dalam Bahasa Inggris

  • Rust menyediakan dukungan bawaan untuk pemrograman konkuren melalui modul standard library std::thread.
  • Thread di Rust memungkinkan beberapa bagian program untuk dieksekusi secara simultan, memanfaatkan prosesor multi-core.

Threads

  • Anda dapat membuat thread menggunakan std::thread::spawn, yang menerima sebuah closure, menjalankannya di thread baru, dan mengembalikan JoinHandle.

  • Kita dapat menunggu thread yang dibuat selesai menggunakan method join dari JoinHandle yang dikembalikan.

    pub fn join(self) -> Result<T>: Menunggu thread yang terkait selesai. Fungsi ini akan kembali segera jika thread yang terkait sudah selesai.

  • Kita sering menggunakan kata kunci move dengan closure yang diteruskan ke thread::spawn karena closure tersebut kemudian akan mengambil kepemilikan nilai yang digunakannya dari environment, sehingga mentransfer kepemilikan nilai-nilai tersebut dari satu thread ke thread lain.

Contoh

use std::thread;
use std::time::Duration;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {i} from the spawned thread!");
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("hi number {i} from the main thread!");
        thread::sleep(Duration::from_millis(1));
    }

    handle.join().unwrap();
}

Menggunakan Smart Pointers, Mutex, dan Channels dalam Threads

Shared Mutable State dengan Arc dan Mutex

Dalam program multi-threaded, jika kita membutuhkan shared mutable state, kita dapat menggunakan:

  • Arc<T>:
    • Sebuah atomic reference-counted smart pointer untuk shared ownership di seluruh thread.
  • Mutex<T>:
    • Sebuah primitive sinkronisasi untuk mutual exclusion agar dapat mengakses data bersama dengan aman.
    • lock(): Mengakuisisi lock. Memblokir thread jika lock sedang dipegang.
    • try_lock(): Mencoba mengakuisisi lock segera. Jika lock tidak dapat diakuisisi, mengembalikan Err.

Contoh

use std::{
    sync::{Arc, Mutex},
    thread,
    time::Duration,
};

struct JobStatus {
    jobs_done: u32,
}

fn main() {
    let status = Arc::new(Mutex::new(JobStatus { jobs_done: 0 }));

    let mut handles = Vec::new();
    for _ in 0..10 {
        let status_shared = Arc::clone(&status);
        let handle = thread::spawn(move || {
            thread::sleep(Duration::from_millis(250));
            // lock dan update jobs_done
            status_shared.lock().unwrap().jobs_done += 1
        });
        handles.push(handle);
    }

    // Menunggu semua pekerjaan selesai.
    for handle in handles {
        handle.join().unwrap();
    }

    println!("Jobs done: {}", status.lock().unwrap().jobs_done);
}
  • Arc<T>: Memungkinkan shared ownership dari Mutex di seluruh thread.
  • Mutex<T>: Memastikan hanya satu thread yang mengakses counter pada satu waktu.

Multiple Readers Single Writer

RwLock (singkatan dari Read-Write Lock) adalah primitive sinkronisasi yang memungkinkan multiple readers atau single writer untuk mengakses data bersama. Ini memastikan akses konkuren yang aman dalam lingkungan multi-threaded.

  • Multiple Readers: Banyak thread dapat memegang read lock secara simultan jika tidak ada thread yang memegang write lock.
  • Single Writer: Hanya satu thread yang dapat memegang write lock, dan ia memiliki akses eksklusif ke data.

RwLock adalah bagian dari modul std::sync dan sering digunakan ketika Anda ingin berbagi data di antara thread dengan operasi baca dan tulis sambil meminimalkan contention.

  • Read Lock
    • read(): Mengakuisisi read lock. Memblokir thread jika write lock sedang dipegang. Mengembalikan immutable reference ke data.
    • try_read(): Mencoba mengakuisisi read lock segera dan mengembalikan Err jika tidak dapat mengakuisisi read lock.
  • Write Lock
    • write(): Mengakuisisi write lock. Memblokir thread jika ada read atau write lock yang dipegang. Mengembalikan mutable reference ke data.
    • try_write(): Mencoba mengakuisisi write lock segera dan mengembalikan Err jika tidak dapat mengakuisisi write lock.

Contoh

use std::{
    sync::{Arc, RwLock},
    thread,
    time::Duration,
};

struct JobStatus {
    jobs_done: u32,
}

fn main() {
    let status = Arc::new(RwLock::new(JobStatus { jobs_done: 0 }));
    let mut handles = Vec::new();

    // Write jobs
    for _ in 0..10 {
        let status_shared = Arc::clone(&status);
        let handle = thread::spawn(move || {
            thread::sleep(Duration::from_millis(250));
            // write lock dan update jobs_done
            status_shared.write().unwrap().jobs_done += 1
        });
        handles.push(handle);
    }

    // Read jobs
    for _ in 0..10 {
        let status_shared = Arc::clone(&status);
        let handle = thread::spawn(move || {
            thread::sleep(Duration::from_millis(250));
            // read lock dan dapatkan jobs_done
            println!("Jobs done: {}", status_shared.read().unwrap().jobs_done);
        });
        handles.push(handle);
    }

    // Menunggu semua pekerjaan selesai.
    for handle in handles {
        handle.join().unwrap();
    }
}
  • Counter dibungkus dalam Arc<RwLock<T>> sehingga dapat dibagikan dengan aman antar thread.
  • Arc menyediakan reference counting untuk thread-safe ownership.
  • write(): Mengakuisisi write lock, memberikan akses mutable eksklusif ke shared counter.
  • read(): Mengakuisisi read lock, memungkinkan banyak thread mengakses counter secara immutable.
  • Lock memastikan bahwa data races dicegah saat mengakses shared counter.

Komunikasi Antar Thread dengan Channels

Rust menyediakan channels untuk komunikasi thread di modul std::sync::mpsc:

  • mpsc: Singkatan dari multiple producers, single consumer.
  • Itu berarti kita dapat membuat banyak Sender menggunakan method clone.

Contoh

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
    let (tx, rx) = mpsc::channel(); // Buat sebuah channel

    // clone Sender
    let txc = tx.clone();
    // Buat thread untuk mengirim pesan
    thread::spawn(move || {
        let messages = vec![1, 2, 3, 4, 5];
        for msg in messages {
            txc.send(msg).unwrap(); // Kirim pesan
            thread::sleep(Duration::from_millis(100));
        }
    });
    
    // Buat thread untuk mengirim pesan
    thread::spawn(move || {
        let messages = vec![6, 7, 8, 9, 10];
        for msg in messages {
            tx.send(msg).unwrap(); // Kirim pesan
            thread::sleep(Duration::from_millis(100));
        }
    });

    // Terima pesan di thread utama
    for received in rx {
        println!("Received: {}", received);
    }
}
  • mpsc::channel membuat sebuah transmitter (tx) dan sebuah receiver (rx).
  • Pesan dikirim melalui Sender tx dan diterima melalui Receiver rx.

Best Practices untuk Multithreading

  • Minimalkan Lock Contention:
    • Jaga critical section (di mana mutex di-lock) sesingkat mungkin untuk menghindari memperlambat thread.
  • Gunakan Arc dan Mutex dengan Bijak:
    • Hindari menggunakan shared state kecuali diperlukan.
    • Lebih suka message passing dengan channels untuk isolasi yang lebih baik.

Referensi


Edit on GitHub

Last updated on

Menjalankan Kode Secara Simultan | Faisal Affan