Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Konkurensi yang Bisa Diperluas (Extensible) dengan Trait Send dan Sync

Yang menarik, hampir semua fitur konkurensi yang udah kita bahas sejauh ini di bab ini adalah bagian dari standard library, bukan bagian dari bahasa Rust itu sendiri. Pilihan kita buat menangani konkurensi tidak cuma terbatas pada apa yang disediakan oleh bahasa atau standard library; kita bisa nulis fitur konkurensi kita sendiri atau memakai yang udah ditulis sama orang lain.

Namun, di antara konsep-konsep konkurensi utama yang tertanam (embedded) di dalam bahasanya ketimbang di standard library adalah marker traits (trait penanda) std::marker yaitu Send dan Sync.

Mengizinkan Transfer Kepemilikan Antar Threads dengan Send

Marker trait Send mengindikasikan bahwa kepemilikan (ownership) dari nilai dengan tipe yang mengimplementasikan Send itu bisa ditransfer antar threads. Hampir setiap tipe di Rust mengimplementasikan Send, tapi ada beberapa pengecualian, termasuk Rc<T>: tipe ini tidak bisa mengimplementasikan Send karena kalau kita meng-clone sebuah nilai Rc<T> lalu mencoba buat mentransfer kepemilikan dari clone tersebut ke thread lain, kedua threads bisa aja meng-update reference count di waktu yang bersamaan. Atas alasan inilah, Rc<T> diimplementasikan buat dipakai di situasi single-threaded di mana kita tidak mau membayar penalti performa (performance penalty) demi keamanan thread.

Oleh karena itu, sistem tipe dan trait bounds Rust memastikan kalau kita tidak bakal bisa secara tidak sengaja mengirim nilai Rc<T> melintasi threads dengan cara yang tidak aman. Pas kita mencoba melakukan ini di Listing 16-14, kita dapat error the trait `Send` is not implemented for `Rc<Mutex<i32>>`. Pas kita ganti jadi pakai Arc<T>, yang mana emang mengimplementasikan Send, kodenya berhasil di-compile.

Tipe apa pun yang secara utuh disusun (composed entirely) dari tipe-tipe yang mengimplementasikan Send bakal secara otomatis ditandai sebagai Send juga. Hampir semua tipe primitif itu Send, kecuali untuk raw pointers (pointer mentah), yang bakal kita bahas di Bab 20.

Mengizinkan Akses dari Banyak Threads dengan Sync

Marker trait Sync mengindikasikan kalau aman buat sebuah tipe yang mengimplementasikan Sync untuk dirujuk (referenced) dari banyak threads. Dengan kata lain, tipe T apa pun mengimplementasikan Sync kalau &T (referensi immutable ke T) mengimplementasikan Send, yang berarti referensi tersebut bisa dikirim dengan aman ke thread lain. Sama kayak Send, semua tipe primitif mengimplementasikan Sync, dan tipe-tipe yang secara utuh disusun dari tipe-tipe yang mengimplementasikan Sync juga bakal mengimplementasikan Sync.

Smart pointer Rc<T> juga tidak mengimplementasikan Sync dengan alasan yang sama kayak kenapa dia tidak mengimplementasikan Send. Tipe RefCell<T> (yang kita bahas di Bab 15) dan keluarga dari tipe Cell<T> yang terkait juga tidak mengimplementasikan Sync. Implementasi dari borrow checking yang dilakukan RefCell<T> saat runtime itu tidak thread-safe (tidak aman di lingkungan banyak utas). Smart pointer Mutex<T> mengimplementasikan Sync dan bisa dipakai buat berbagi akses dengan banyak threads, seperti yang kita lihat di “Berbagi Mutex<T> di Antara Beberapa Threads”.

Mengimplementasikan Send dan Sync secara Manual Itu Unsafe (Tidak Aman)

Karena tipe yang secara utuh disusun dari tipe-tipe lain yang mengimplementasikan trait Send dan Sync itu juga otomatis mengimplementasikan Send dan Sync, kita tidak perlu mengimplementasikan trait-trait tersebut secara manual. Sebagai marker traits, mereka bahkan tidak punya method apa pun buat diimplementasikan. Mereka cuma berguna buat memaksakan (enforcing) aturan baku (invariants) yang berkaitan dengan konkurensi.

Mengimplementasikan trait-trait ini secara manual melibatkan penulisan kode Rust yang unsafe. Kita bakal ngomongin soal memakai kode Rust yang unsafe di Bab 20; buat sekarang, informasi pentingnya adalah bahwa membangun tipe konkuren baru yang tidak disusun dari bagian-bagian yang Send dan Sync membutuhkan pemikiran yang ekstra hati-hati buat mempertahankan jaminan keamanannya (safety guarantees). “The Rustonomicon” punya lebih banyak informasi soal jaminan-jaminan ini dan gimana cara mempertahankannya.

Ringkasan

Ini bukan terakhir kalinya kita bakal melihat konkurensi di buku ini: bab berikutnya berfokus pada pemrograman async, dan project di Bab 21 bakal memakai konsep-konsep di bab ini di dalam situasi yang lebih realistis ketimbang contoh-contoh kecil yang dibahas di sini.

Seperti yang disebutkan sebelumnya, karena cuma sebagian kecil dari cara Rust menangani konkurensi itu yang menjadi bagian dari bahasanya, banyak solusi konkurensi diimplementasikan dalam bentuk crates. Crate-crate ini berevolusi lebih cepat daripada standard library, jadi pastikan kita mencari secara online buat crates yang paling mutakhir (state-of-the-art) buat dipakai di situasi- situasi multithreaded.

Standard library Rust menyediakan channels buat pengiriman pesan (message passing) dan tipe-tipe smart pointer, seperti Mutex<T> dan Arc<T>, yang aman buat dipakai di konteks konkuren. Sistem tipe dan borrow checker memastikan kalau kode yang memakai solusi-solusi ini tidak bakal berujung pada data races atau referensi yang tidak valid. Begitu kita berhasil membikin kode kita bisa di-compile, kita bisa bernapas lega karena dia bakal jalan dengan bahagia di atas banyak threads tanpa jenis bugs yang susah dilacak kayak yang biasa terjadi di bahasa pemrograman lain. Pemrograman konkuren bukan lagi konsep yang perlu ditakutkan: maju terus dan bikin program kita konkuren tanpa rasa takut!