unisbadri.com » Python Java Golang Typescript Kotlin Ruby Rust Dart PHP
Multi Threading

Multi Threading #

Multi-threading adalah teknik yang memungkinkan program untuk melakukan beberapa tugas secara bersamaan dengan membagi pekerjaan ke dalam beberapa “thread”. Di Rust, multi-threading sangat didukung dengan fitur-fitur yang aman dari sisi memori dan data race (kondisi balapan data), berkat sistem kepemilikan (ownership) dan aturan pinjaman (borrowing) yang kuat. Rust memastikan bahwa data yang diakses oleh beberapa thread tetap aman dan bebas dari kesalahan yang umum terjadi pada multi-threading seperti data race.

Berikut adalah penjelasan lengkap mengenai multi-threading dalam Rust:

Dasar-dasar Multi-threading di Rust #

Rust menyediakan fasilitas untuk membuat thread melalui modul standar std::thread. Fitur dasar dari multi-threading di Rust meliputi pembuatan thread, komunikasi antar thread, dan penanganan kondisi balapan.

Membuat Thread Baru #

Anda dapat membuat thread baru di Rust menggunakan fungsi thread::spawn. Fungsi ini menerima closure sebagai argumen, yang kemudian dieksekusi di thread baru.

Contoh Membuat Thread Baru:

use std::thread;

fn main() {
    thread::spawn(|| {
        for i in 1..10 {
            println!("Hello dari thread baru: {}", i);
        }
    });

    for i in 1..5 {
        println!("Hello dari thread utama: {}", i);
    }
}

Penjelasan:

  • thread::spawn membuat thread baru yang menjalankan closure yang dicetak dalam loop.
  • Thread utama tetap berjalan secara bersamaan dengan thread baru.

Catatan: Thread utama mungkin selesai sebelum thread baru, yang dapat menyebabkan thread baru tidak memiliki cukup waktu untuk mengeksekusi semua iterasi loop. Untuk memastikan semua thread selesai sebelum program berakhir, Anda harus menangani JoinHandle dari thread.

Bergabung dengan Thread (Thread Joining) #

Agar program Rust menunggu thread selesai sebelum melanjutkan eksekusi, Anda bisa menggunakan metode join pada JoinHandle, yang dikembalikan oleh thread::spawn.

Contoh Thread Joining:

use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("Hello dari thread baru: {}", i);
        }
    });

    for i in 1..5 {
        println!("Hello dari thread utama: {}", i);
    }

    handle.join().unwrap(); // Tunggu thread baru selesai
}

Penjelasan:

  • handle.join().unwrap() memblokir eksekusi di thread utama sampai thread baru selesai.

Berbagi Data Antar Thread #

Rust mendukung berbagi data antar thread dengan cara yang aman melalui penggunaan smart pointers seperti Arc (Atomic Reference Counted) dan mekanisme sinkronisasi seperti Mutex.

Berbagi Data dengan Arc #

Arc<T> (Atomic Reference Counted) digunakan untuk berbagi kepemilikan data yang bersifat immutable antara beberapa thread. Arc memungkinkan beberapa thread untuk memiliki referensi ke data yang sama tanpa memerlukan peminjaman mutable, karena Arc menjaga jumlah referensi secara aman di lingkungan multi-threading.

Contoh Penggunaan Arc:

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

fn main() {
    let numbers = Arc::new(vec![1, 2, 3, 4, 5]);

    let mut handles = vec![];

    for _ in 0..3 {
        let numbers = Arc::clone(&numbers);
        let handle = thread::spawn(move || {
            println!("Sum: {}", numbers.iter().sum::<i32>());
        });
        handles.push(handle);
    }

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

Penjelasan:

  • Arc::new(vec![1, 2, 3, 4, 5]) membuat objek Arc yang membungkus vektor.
  • Arc::clone(&numbers) menghasilkan salinan dari Arc yang dapat dibagikan ke thread lain.
  • Setiap thread memanggil metode sum pada vektor yang dibungkus oleh Arc.

Berbagi Data yang Bisa Diubah dengan Mutex #

Untuk berbagi data yang bisa diubah antar thread, Anda perlu menggunakan Mutex (Mutual Exclusion). Mutex memastikan bahwa hanya satu thread yang dapat mengakses data yang dilindungi oleh Mutex pada satu waktu, sehingga mencegah kondisi balapan data.

Contoh Penggunaan Mutex:

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

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

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

    println!("Counter: {}", *counter.lock().unwrap());
}

Penjelasan:

  • Mutex::new(0) membuat objek Mutex yang membungkus nilai 0.
  • Arc::new(Mutex::new(0)) membuat objek Arc yang membungkus Mutex, memungkinkan beberapa thread untuk mengakses Mutex yang sama.
  • counter.lock() mengunci Mutex, memberikan akses eksklusif ke data di dalamnya untuk thread tersebut.
  • *num += 1 menambah nilai counter di dalam Mutex.

Kondisi Balapan Data dan Penanganannya #

Data race terjadi ketika dua atau lebih thread mengakses data bersama secara bersamaan, dan setidaknya satu akses adalah operasi tulis, tanpa adanya sinkronisasi yang tepat. Rust mencegah kondisi balapan data pada waktu kompilasi melalui sistem kepemilikan dan peminjaman. Penggunaan Arc dan Mutex adalah cara utama untuk menghindari data race ketika berbagi data antar thread.

Komunikasi Antar Thread dengan Channels #

Rust menyediakan fitur channels untuk komunikasi antar thread. Channel adalah mekanisme yang memungkinkan pengiriman pesan antara thread secara aman.

Membuat Channel #

Rust memiliki dua komponen utama dalam channels: sender (pengirim) dan receiver (penerima). Data yang dikirim melalui sender dapat diterima oleh receiver.

Contoh Penggunaan Channels:

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

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

    thread::spawn(move || {
        let val = String::from("hello");
        tx.send(val).unwrap();
    });

    let received = rx.recv().unwrap();
    println!("Received: {}", received);
}

Penjelasan:

  • mpsc::channel() membuat channel dengan sender (tx) dan receiver (rx).
  • tx.send(val) mengirim data melalui sender.
  • rx.recv() menerima data yang dikirim melalui channel.

Multi-Producer Single-Consumer (MPSC) #

Rust mendukung pengiriman data dari beberapa thread ke satu receiver menggunakan konsep multi-producer single-consumer (MPSC).

Contoh MPSC:

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

fn main() {
    let (tx, rx) = mpsc::channel();
    let tx1 = tx.clone();

    thread::spawn(move || {
        let val = String::from("hello from tx");
        tx.send(val).unwrap();
    });

    thread::spawn(move || {
        let val = String::from("hello from tx1");
        tx1.send(val).unwrap();
    });

    for received in rx {
        println!("Received: {}", received);
    }
}

Penjelasan:

  • tx.clone() membuat salinan sender, memungkinkan beberapa thread untuk mengirim data melalui channel yang sama.
  • for received in rx digunakan untuk menerima data dari channel secara iteratif.

Parallelism vs Concurrency #

Dalam Rust, parallelism adalah eksekusi tugas-tugas secara bersamaan untuk meningkatkan kinerja, sementara concurrency mengacu pada pengelolaan beberapa tugas secara bersamaan yang mungkin tidak dieksekusi pada waktu yang sama. Rust menyediakan alat untuk kedua pendekatan ini, dan pilihan yang tepat tergantung pada kebutuhan aplikasi.

Asynchronous Programming #

Meskipun tidak sepenuhnya bagian dari multi-threading, Rust mendukung programming asynchronous yang memungkinkan tugas-tugas untuk berjalan secara concurrent tanpa memblokir thread.

Contoh Asynchronous dengan async/await:

use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    let task1 = tokio::spawn(async {
        sleep(Duration::from_secs(1)).await;
        println!("Task 1 selesai");
    });

    let task2 = tokio::spawn(async {
        sleep(Duration::from_secs(2)).await;
        println!("Task 2 selesai");
    });

    task1.await.unwrap();
    task2.await.unwrap();
}

Penjelasan:

  • tokio::spawn digunakan untuk menjalankan tugas asynchronous secara concurrent.
  • sleep(Duration::from_secs(x)) mensimulasikan penundaan asynchronous.
  • await menunggu tugas asynchronous selesai.

Kesimpulan #

Rust menyediakan alat dan fitur yang kuat untuk bekerja dengan multi-threading, termasuk kemampuan untuk membuat thread baru, berbagi data antar thread dengan aman, dan berkomunikasi antar thread menggunakan channels. Dengan bantuan sistem kepemilikan dan peminjaman, Rust memastikan bahwa kode multi-threaded aman dari kesalahan memori dan kondisi balapan data yang umum terjadi di banyak bahasa pemrograman lainnya. Memahami konsep-konsep ini akan membantu Anda menulis program Rust yang efisien, aman, dan dapat diandalkan di lingkungan multi-threaded.

« Crates
I/O »