Sync #
Konkurensi adalah salah satu domain yang paling sulit dalam pemrograman — data race, deadlock, dan kondisi balapan adalah bug yang sulit direproduksi dan lebih sulit lagi di-debug. Di bahasa lain, developer bergantung pada konvensi dan disiplin manual untuk menghindari masalah ini. Rust mengambil pendekatan yang fundamental berbeda: kompiler menjamin tidak ada data race bisa lolos ke runtime. Bukan dengan melarang konkurensi, tapi dengan menjadikan state sharing yang tidak aman mustahil untuk dikompilasi. Artikel ini membahas primitif konkurensi di standard library Rust — Arc, Mutex, RwLock, channel mpsc, dan tipe Atomic — serta pola desain yang memastikan kode konkuren kamu benar secara definitif.
Mengapa Konkurensi Rust Berbeda #
Di Rust, dua trait menentukan apakah suatu tipe bisa digunakan secara konkuren:
Send— tipe yang bisa dipindahkan ke thread lain. Hampir semua tipeSend, kecuali yang mengandung pointer mentah atau reference non-thread-safe sepertiRc<T>.Sync— tipe yang bisa direferensikan dari thread lain (yaitu&TbersifatSend). Tipe yangSyncaman diakses dari banyak thread secara bersamaan.
Kedua trait ini di-implementasikan secara otomatis oleh compiler berdasarkan komposisi tipe. Jika kamu mencoba mengirim tipe yang tidak Send ke thread lain, kode tidak akan dikompilasi.
use std::thread;
use std::rc::Rc;
use std::sync::Arc;
fn main() {
// ANTI-PATTERN: Rc tidak Send — tidak bisa dikirim ke thread lain
let rc = Rc::new(42);
// thread::spawn(move || println!("{}", rc));
// ERROR: `Rc<i32>` cannot be sent between threads safely
// BENAR: Arc adalah versi thread-safe dari Rc
let arc = Arc::new(42);
let arc_clone = Arc::clone(&arc);
thread::spawn(move || {
println!("Di thread lain: {}", arc_clone);
}).join().unwrap();
println!("Di main thread: {}", arc);
}
flowchart TD
A[Tipe T] --> B{Implements Send?}
B -- Ya --> C{Implements Sync?}
B -- Tidak --> D["Tidak bisa dipindah ke thread lain\nContoh: Rc<T>, *mut T"]
C -- Ya --> E["Aman untuk shared reference antar thread\nContoh: Arc<T>, Mutex<T>"]
C -- Tidak --> F["Bisa dipindah tapi tidak di-share\nContoh: Cell<T>, RefCell<T>"]
style D fill:#ffebee
style E fill:#e8f5e9
style F fill:#fff3e0Thread — Membuat dan Mengelola #
Standard library menyediakan std::thread untuk membuat thread OS.
use std::thread;
use std::time::Duration;
fn main() {
// Membuat thread — closure di-move ke thread baru
let handle = thread::spawn(|| {
for i in 0..5 {
println!("Thread anak: {}", i);
thread::sleep(Duration::from_millis(10));
}
});
// Main thread terus berjalan
for i in 0..3 {
println!("Main thread: {}", i);
thread::sleep(Duration::from_millis(15));
}
// join() — tunggu thread selesai, mengembalikan Result
handle.join().unwrap();
// Mengirim data ke thread dengan move closure
let data = vec![1, 2, 3, 4, 5];
let handle = thread::spawn(move || {
// data di-move ke dalam thread
let jumlah: i32 = data.iter().sum();
jumlah // nilai return dari thread
});
let hasil = handle.join().unwrap(); // ambil nilai return
println!("Jumlah: {}", hasil);
// Thread dengan nama — berguna untuk debugging
let handle = thread::Builder::new()
.name("worker-thread".to_string())
.stack_size(4 * 1024 * 1024) // 4MB stack
.spawn(|| {
println!("Thread: {:?}", thread::current().name());
})
.unwrap();
handle.join().unwrap();
// Menjalankan banyak thread sekaligus
let handles: Vec<_> = (0..5).map(|i| {
thread::spawn(move || {
println!("Worker {} selesai", i);
i * i // return nilai
})
}).collect();
let hasil: Vec<i32> = handles.into_iter()
.map(|h| h.join().unwrap())
.collect();
println!("{:?}", hasil); // [0, 1, 4, 9, 16]
}
Arc<T> — Shared Ownership Antar Thread #
Arc<T> (Atomically Reference Counted) adalah versi thread-safe dari Rc<T>. Ia memungkinkan banyak thread memiliki referensi ke data yang sama, dengan reference count yang dikelola secara atomic.
use std::sync::Arc;
use std::thread;
fn main() {
// Arc memungkinkan shared ownership antar thread
let data = Arc::new(vec![1, 2, 3, 4, 5]);
let mut handles = vec![];
for i in 0..3 {
let data_clone = Arc::clone(&data); // clone Arc, bukan data
let handle = thread::spawn(move || {
println!("Thread {}: jumlah = {}", i, data_clone.iter().sum::<i32>());
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
// data masih valid di sini
println!("Main: {:?}", data);
// Arc hanya untuk immutable shared data
// Untuk mutable shared data, kombinasikan dengan Mutex
let counter = Arc::new(std::sync::Mutex::new(0i32));
let handles: Vec<_> = (0..5).map(|_| {
let counter = Arc::clone(&counter);
thread::spawn(move || {
let mut nilai = counter.lock().unwrap();
*nilai += 1;
})
}).collect();
for handle in handles {
handle.join().unwrap();
}
println!("Counter akhir: {}", *counter.lock().unwrap()); // 5
}
Arc::clone()lebih efisien dari.clone()biasa — ia hanya menambah reference count secara atomic, tidak menyalin data. GunakanArc::clone(&arc)bukanarc.clone()agar niat jelas di kode.
Mutex<T> — Mutual Exclusion untuk Mutable State #
Mutex<T> (Mutual Exclusion) memastikan hanya satu thread yang bisa mengakses data di dalamnya pada satu waktu. Di Rust, data dijaga di dalam Mutex — kamu tidak bisa mengakses data tanpa mengunci Mutex terlebih dahulu.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Mutex membungkus data — tidak bisa diakses tanpa lock
let mutex = Mutex::new(0i32);
// lock() mengembalikan MutexGuard — released otomatis saat keluar scope
{
let mut nilai = mutex.lock().unwrap(); // blok sampai lock tersedia
*nilai += 1;
} // MutexGuard di-drop di sini — lock dilepas
println!("{}", mutex.lock().unwrap()); // 1
// Pola umum: Arc<Mutex<T>> untuk shared mutable state
let shared = Arc::new(Mutex::new(Vec::<i32>::new()));
let handles: Vec<_> = (0..5).map(|i| {
let shared = Arc::clone(&shared);
thread::spawn(move || {
let mut data = shared.lock().unwrap();
data.push(i);
})
}).collect();
for handle in handles {
handle.join().unwrap();
}
let data = shared.lock().unwrap();
let mut hasil = data.clone();
drop(data); // lepas lock sebelum operasi lebih lanjut
hasil.sort();
println!("{:?}", hasil); // [0, 1, 2, 3, 4] — urutan tidak dijamin tanpa sort
}
Menghindari Deadlock #
Deadlock terjadi saat dua atau lebih thread saling menunggu lock yang dipegang oleh thread lain. Rust tidak bisa mencegah deadlock di compile time, tapi ada pola yang membantu.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let mutex_a = Arc::new(Mutex::new(0i32));
let mutex_b = Arc::new(Mutex::new(0i32));
// ANTI-PATTERN: urutan lock yang berbeda antar thread — potensi deadlock
let a = Arc::clone(&mutex_a);
let b = Arc::clone(&mutex_b);
let t1 = thread::spawn(move || {
let _lock_a = a.lock().unwrap(); // ambil A
thread::sleep(std::time::Duration::from_millis(1));
let _lock_b = b.lock().unwrap(); // tunggu B — bisa deadlock jika t2 pegang B
});
let a = Arc::clone(&mutex_a);
let b = Arc::clone(&mutex_b);
let t2 = thread::spawn(move || {
let _lock_b = b.lock().unwrap(); // ambil B
thread::sleep(std::time::Duration::from_millis(1));
let _lock_a = a.lock().unwrap(); // tunggu A — bisa deadlock jika t1 pegang A
});
// BENAR: selalu kunci dalam urutan yang sama di semua thread
let a = Arc::clone(&mutex_a);
let b = Arc::clone(&mutex_b);
let t3 = thread::spawn(move || {
let _lock_a = a.lock().unwrap(); // selalu A dulu
let _lock_b = b.lock().unwrap(); // baru B
});
// BENAR: lepas lock sesegera mungkin — minimalkan waktu di dalam critical section
let mutex = Arc::new(Mutex::new(0i32));
let m = Arc::clone(&mutex);
thread::spawn(move || {
// ANTI-PATTERN: komputasi panjang di dalam lock
let mut nilai = m.lock().unwrap();
let hasil = komputasi_berat(); // blok thread lain selama ini
*nilai = hasil;
// BENAR: komputasi di luar lock
let hasil = komputasi_berat();
let mut nilai = m.lock().unwrap();
*nilai = hasil; // lock hanya untuk assignment
});
// try_lock — coba lock tanpa memblok
let mutex = Mutex::new(42);
match mutex.try_lock() {
Ok(nilai) => println!("Berhasil lock: {}", *nilai),
Err(_) => println!("Lock sedang dipakai, coba lagi nanti"),
}
t1.join().unwrap();
t2.join().unwrap();
t3.join().unwrap();
}
fn komputasi_berat() -> i32 { 42 }
RwLock<T> — Multiple Readers, One Writer #
RwLock<T> (Read-Write Lock) memungkinkan banyak reader atau satu writer secara bersamaan. Ini lebih efisien dari Mutex untuk skenario di mana operasi baca jauh lebih sering dari operasi tulis.
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let data = Arc::new(RwLock::new(vec![1, 2, 3, 4, 5]));
// Banyak reader bisa berjalan bersamaan
let handles: Vec<_> = (0..4).map(|i| {
let data = Arc::clone(&data);
thread::spawn(move || {
let baca = data.read().unwrap(); // read lock — bisa bersamaan dengan reader lain
println!("Reader {}: jumlah = {}", i, baca.iter().sum::<i32>());
// read lock dilepas saat baca keluar scope
})
}).collect();
for handle in handles {
handle.join().unwrap();
}
// Hanya satu writer pada satu waktu
{
let mut tulis = data.write().unwrap(); // write lock — eksklusif
tulis.push(6);
println!("Setelah tulis: {:?}", *tulis);
} // write lock dilepas
// Baca lagi setelah tulis
println!("Hasil akhir: {:?}", *data.read().unwrap());
// Kapan RwLock lebih baik dari Mutex?
// RwLock bagus untuk: cache, konfigurasi, registry yang sering dibaca tapi jarang diubah
// Mutex lebih sederhana untuk: counter, queue, state yang sering dimodifikasi
}
sequenceDiagram
participant T1 as Thread 1 (Reader)
participant T2 as Thread 2 (Reader)
participant T3 as Thread 3 (Writer)
participant RW as RwLock
T1->>RW: read()
RW-->>T1: ReadGuard ✓
T2->>RW: read()
RW-->>T2: ReadGuard ✓ (bersamaan dengan T1)
T3->>RW: write()
Note over T3,RW: Menunggu T1 dan T2 selesai
T1->>RW: drop ReadGuard
T2->>RW: drop ReadGuard
RW-->>T3: WriteGuard ✓ (sekarang eksklusif)
T3->>RW: drop WriteGuardChannel mpsc — Komunikasi Antar Thread #
Channel adalah mekanisme komunikasi berbasis message passing — thread berkomunikasi dengan mengirim data, bukan berbagi state. Ini sering lebih mudah di-reason daripada shared state.
mpsc adalah singkatan dari multiple producer, single consumer — banyak sender, satu receiver.
use std::sync::mpsc;
use std::thread;
fn main() {
// Membuat channel — tx untuk kirim, rx untuk terima
let (tx, rx) = mpsc::channel();
// Kirim dari thread lain
let tx_clone = tx.clone(); // clone sender untuk multiple producer
thread::spawn(move || {
tx_clone.send(String::from("pesan dari thread 1")).unwrap();
tx_clone.send(String::from("pesan kedua dari thread 1")).unwrap();
});
thread::spawn(move || {
tx.send(String::from("pesan dari thread 2")).unwrap();
});
// Terima semua pesan — recv() memblok sampai ada pesan atau semua sender ter-drop
for pesan in rx {
println!("Diterima: {}", pesan);
}
// Loop berakhir saat semua sender ter-drop
}
Pola Producer-Consumer #
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
#[derive(Debug)]
enum Tugas {
Proses(String),
Selesai,
}
fn main() {
let (tx, rx) = mpsc::channel::<Tugas>();
// Worker thread — consumer
let worker = thread::spawn(move || {
let mut diproses = 0;
loop {
match rx.recv().unwrap() {
Tugas::Proses(data) => {
println!("Memproses: {}", data);
thread::sleep(Duration::from_millis(10)); // simulasi kerja
diproses += 1;
}
Tugas::Selesai => {
println!("Worker selesai, total diproses: {}", diproses);
break;
}
}
}
});
// Producer — kirim tugas
for i in 0..5 {
tx.send(Tugas::Proses(format!("data-{}", i))).unwrap();
}
tx.send(Tugas::Selesai).unwrap(); // sinyal berhenti
worker.join().unwrap();
// Channel dengan batas (bounded channel) — sync_channel
let (tx, rx) = mpsc::sync_channel::<i32>(3); // buffer maksimal 3 pesan
thread::spawn(move || {
for i in 0..10 {
println!("Mengirim {}", i);
tx.send(i).unwrap(); // memblok jika buffer penuh
}
});
for pesan in rx {
thread::sleep(Duration::from_millis(50)); // consumer lebih lambat
println!("Diterima: {}", pesan);
}
}
try_recv dan recv_timeout #
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel::<i32>();
thread::spawn(move || {
thread::sleep(Duration::from_millis(100));
tx.send(42).unwrap();
});
// try_recv — tidak memblok, segera kembali
match rx.try_recv() {
Ok(nilai) => println!("Diterima: {}", nilai),
Err(mpsc::TryRecvError::Empty) => println!("Belum ada pesan"),
Err(mpsc::TryRecvError::Disconnected) => println!("Sender sudah ter-drop"),
}
// recv_timeout — memblok dengan batas waktu
match rx.recv_timeout(Duration::from_millis(200)) {
Ok(nilai) => println!("Diterima dalam timeout: {}", nilai),
Err(mpsc::RecvTimeoutError::Timeout) => println!("Timeout!"),
Err(mpsc::RecvTimeoutError::Disconnected) => println!("Disconnected"),
}
}
Atomic — Operasi Lock-Free #
Tipe Atomic memungkinkan operasi baca-tulis yang aman secara thread tanpa lock. Mereka diimplementasikan menggunakan instruksi CPU khusus (compare-and-swap, fetch-and-add) yang dijamin atomic.
use std::sync::atomic::{AtomicI32, AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
fn main() {
// AtomicI32 — counter tanpa Mutex
let counter = Arc::new(AtomicI32::new(0));
let handles: Vec<_> = (0..10).map(|_| {
let counter = Arc::clone(&counter);
thread::spawn(move || {
// fetch_add — tambah dan kembalikan nilai lama, secara atomic
counter.fetch_add(1, Ordering::SeqCst);
})
}).collect();
for handle in handles {
handle.join().unwrap();
}
println!("Counter: {}", counter.load(Ordering::SeqCst)); // 10
// AtomicBool — flag thread-safe
let berjalan = Arc::new(AtomicBool::new(true));
let berjalan_clone = Arc::clone(&berjalan);
let worker = thread::spawn(move || {
let mut iterasi = 0;
while berjalan_clone.load(Ordering::Relaxed) {
iterasi += 1;
thread::sleep(std::time::Duration::from_millis(1));
}
println!("Worker berhenti setelah {} iterasi", iterasi);
});
thread::sleep(std::time::Duration::from_millis(10));
berjalan.store(false, Ordering::Relaxed); // sinyal berhenti
worker.join().unwrap();
// AtomicUsize — ID generator yang aman
static COUNTER: AtomicUsize = AtomicUsize::new(0);
fn generate_id() -> usize {
COUNTER.fetch_add(1, Ordering::SeqCst)
}
let handles: Vec<_> = (0..5).map(|_| {
thread::spawn(|| generate_id())
}).collect();
let ids: Vec<usize> = handles.into_iter().map(|h| h.join().unwrap()).collect();
println!("{:?}", ids); // setiap ID unik, tapi urutan tidak tentu
}
Memory Ordering #
Memory ordering menentukan bagaimana operasi atomic berinteraksi dengan operasi memori lainnya di sekitarnya.
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
fn main() {
// Ordering::Relaxed — hanya atomicity, tanpa jaminan urutan
// Cocok untuk: counter sederhana yang tidak bergantung pada nilai lain
let counter = AtomicI32::new(0);
counter.fetch_add(1, Ordering::Relaxed);
// Ordering::Release + Acquire — sinkronisasi antara producer dan consumer
// Release: semua tulis sebelumnya visible ke thread yang melakukan Acquire
// Cocok untuk: flag yang menandakan data sudah siap
static DATA_SIAP: AtomicBool = AtomicBool::new(false);
static mut DATA: i32 = 0;
// Producer thread
// unsafe { DATA = 42; } // tulis data
// DATA_SIAP.store(true, Ordering::Release); // tandai siap
// Consumer thread
// while !DATA_SIAP.load(Ordering::Acquire) {} // tunggu sampai siap
// unsafe { println!("{}", DATA); } // aman baca sekarang
// Ordering::SeqCst — urutan total, paling kuat dan paling aman
// Cocok untuk: ketika tidak yakin, atau ketika correctness lebih penting dari performa
counter.store(0, Ordering::SeqCst);
counter.fetch_add(1, Ordering::SeqCst);
println!("{}", counter.load(Ordering::SeqCst));
}
| Ordering | Kekuatan | Performa | Kapan Digunakan |
|---|---|---|---|
Relaxed | Terendah | Terbaik | Counter sederhana tanpa dependensi |
Acquire | Sedang | Baik | Baca flag yang menandakan data siap |
Release | Sedang | Baik | Tulis flag setelah data siap |
AcqRel | Sedang | Baik | Read-modify-write (fetch_add, compare_exchange) |
SeqCst | Tertinggi | Paling lambat | Default aman, ketika tidak yakin |
Untuk sebagian besar kasus, gunakan Ordering::SeqCst — ini paling mudah di-reason dan perbedaan performanya hanya signifikan di hot path dengan jutaan operasi per detik. Optimasi ke ordering yang lebih lemah hanya setelah profiling membuktikan perlunya.Pola Desain Konkurensi #
Thread Pool Sederhana dengan Channel #
use std::sync::{Arc, Mutex};
use std::sync::mpsc;
use std::thread;
type Job = Box<dyn FnOnce() + Send + 'static>;
struct ThreadPool {
workers: Vec<thread::JoinHandle<()>>,
sender: mpsc::Sender<Option<Job>>,
}
impl ThreadPool {
fn new(ukuran: usize) -> Self {
let (sender, receiver) = mpsc::channel::<Option<Job>>();
let receiver = Arc::new(Mutex::new(receiver));
let workers = (0..ukuran).map(|id| {
let receiver = Arc::clone(&receiver);
thread::spawn(move || loop {
let pesan = receiver.lock().unwrap().recv().unwrap();
match pesan {
Some(job) => {
println!("Worker {} mengerjakan tugas", id);
job();
}
None => {
println!("Worker {} berhenti", id);
break;
}
}
})
}).collect();
ThreadPool { workers, sender }
}
fn execute<F: FnOnce() + Send + 'static>(&self, f: F) {
self.sender.send(Some(Box::new(f))).unwrap();
}
fn shutdown(self) {
for _ in &self.workers {
self.sender.send(None).unwrap(); // kirim sinyal berhenti ke setiap worker
}
for worker in self.workers {
worker.join().unwrap();
}
}
}
fn main() {
let pool = ThreadPool::new(4);
for i in 0..8 {
pool.execute(move || {
println!("Tugas {} selesai di thread {:?}", i, thread::current().id());
});
}
pool.shutdown();
}
Shared Cache dengan RwLock #
use std::sync::{Arc, RwLock};
use std::collections::HashMap;
use std::thread;
struct Cache {
data: RwLock<HashMap<String, String>>,
}
impl Cache {
fn new() -> Arc<Self> {
Arc::new(Cache {
data: RwLock::new(HashMap::new()),
})
}
fn get(&self, kunci: &str) -> Option<String> {
self.data.read().unwrap().get(kunci).cloned()
}
fn set(&self, kunci: String, nilai: String) {
self.data.write().unwrap().insert(kunci, nilai);
}
fn get_or_compute(&self, kunci: &str, hitung: impl FnOnce() -> String) -> String {
// Cek dengan read lock dulu (cepat)
if let Some(nilai) = self.get(kunci) {
return nilai;
}
// Jika tidak ada, hitung dan simpan
let nilai = hitung();
self.set(kunci.to_string(), nilai.clone());
nilai
}
}
fn main() {
let cache = Cache::new();
// Banyak reader bersamaan
let handles: Vec<_> = (0..5).map(|i| {
let cache = Arc::clone(&cache);
thread::spawn(move || {
let kunci = format!("kunci-{}", i % 3);
let nilai = cache.get_or_compute(&kunci, || {
println!("Menghitung untuk {}", kunci);
format!("nilai-{}", i)
});
println!("Thread {}: {} = {}", i, kunci, nilai);
})
}).collect();
for handle in handles {
handle.join().unwrap();
}
}
Once — Inisialisasi Sekali #
use std::sync::Once;
use std::thread;
static INIT: Once = Once::new();
static mut KONFIGURASI: Option<String> = None;
fn dapatkan_konfigurasi() -> &'static str {
INIT.call_once(|| {
// Hanya dieksekusi sekali, meskipun dipanggil dari banyak thread
println!("Menginisialisasi konfigurasi...");
unsafe {
KONFIGURASI = Some(String::from("konfigurasi-global"));
}
});
unsafe { KONFIGURASI.as_ref().unwrap() }
}
fn main() {
let handles: Vec<_> = (0..5).map(|_| {
thread::spawn(|| {
println!("Konfigurasi: {}", dapatkan_konfigurasi());
})
}).collect();
for handle in handles {
handle.join().unwrap();
}
// "Menginisialisasi konfigurasi..." hanya muncul sekali
}
Memilih Primitif Konkurensi yang Tepat #
Gunakan Arc<Mutex<T>> jika:
✓ Data perlu dimodifikasi dari banyak thread
✓ Operasi baca dan tulis sama seringnya
✓ Seksi kritis singkat (kurang dari beberapa mikrodetik)
Gunakan Arc<RwLock<T>> jika:
✓ Data jauh lebih sering dibaca dari dimodifikasi
✓ Operasi baca bisa berjalan lama (tidak apa-apa reader lain ikut)
✓ Contoh: cache, konfigurasi, registry
Gunakan mpsc channel jika:
✓ Thread berkomunikasi dengan mengirim pesan
✓ Ownership data perlu berpindah antar thread (bukan shared)
✓ Pola producer-consumer yang jelas
✓ Ingin menghindari shared state sepenuhnya
Gunakan Atomic jika:
✓ Operasinya sederhana: increment, flag, ID generator
✓ Perlu performa maksimum tanpa overhead lock
✓ Tidak ada dependensi antar field yang berbeda
Hindari shared mutable state jika:
✗ Komunikasi antar thread bisa digantikan message passing
✗ Data bisa di-partition — setiap thread punya datanya sendiri
✗ Hasil bisa dikumpulkan setelah thread selesai (join + merge)
flowchart TD
A{Data perlu dibagi antar thread?} -- Ya --> B{Perlu dimodifikasi?}
A -- Tidak --> C["Arc<T> saja — shared immutable"]
B -- Ya --> D{Frekuensi baca vs tulis?}
B -- Tidak --> C
D -- Baca >> Tulis --> E["Arc<RwLock<T>>\nCache, konfigurasi"]
D -- Seimbang --> F["Arc<Mutex<T>>\nCounter, queue"]
D -- Operasi sederhana --> G["Atomic\nCounter, flag, ID"]
A -- Tidak --> H{Thread perlu berkomunikasi?}
H -- Ya --> I["mpsc channel\nProducer-consumer"]
H -- Tidak --> J["Thread independen\nJoin + collect hasil"]
style C fill:#e8f5e9
style E fill:#e3f2fd
style F fill:#fff3e0
style G fill:#e8f5e9
style I fill:#fce4ecRingkasan #
SenddanSyncadalah jaminan compile-time — compiler memastikan tipe yang tidak thread-safe tidak bisa dikirim ke thread lain. Ini adalah fondasi “fearless concurrency” Rust.Arc<T>untuk shared ownership — versi thread-safe dariRc<T>. GunakanArc::clone(&arc)bukanarc.clone()agar niat jelas.Arcsendiri hanya untuk data immutable.Arc<Mutex<T>>untuk shared mutable state —Mutexmenjamin data hanya bisa diakses oleh satu thread.MutexGuarddilepas otomatis saat keluar scope — manfaatkan ini untuk meminimalkan waktu lock.Arc<RwLock<T>>untuk read-heavy workload — banyak reader bisa berjalan bersamaan, hanya writer yang eksklusif. Lebih efisien dariMutexjika rasio baca jauh lebih tinggi dari tulis.mpscchannel untuk message passing — kirim data antar thread dengan transfer ownership, bukan sharing. Pola ini menghilangkan kebutuhan lock dan lebih mudah di-reason.Atomicuntuk operasi lock-free sederhana —AtomicI32,AtomicBool,AtomicUsizeuntuk counter, flag, dan ID generator tanpa overhead Mutex. GunakanSeqCstsebagai default ordering.- Hindari deadlock dengan urutan lock konsisten — selalu kunci multiple mutex dalam urutan yang sama di semua thread. Minimalkan kode di dalam critical section.
- Pilih message passing lebih dulu — jika data bisa dikomunikasikan lewat channel daripada di-share, itu biasanya desain yang lebih sederhana dan lebih mudah di-maintain.