Topik ini membahas ref dan software transactional memory (STM), mekanisme Clojure untuk mengoordinasikan perubahan state pada beberapa identitas sekaligus. Pembahasan mencakup transaction dengan dosync, fungsi alter untuk koordinasi antar ref, fungsi commute untuk operasi yang tidak peduli urutan, serta perilaku retry, yaitu pengulangan transaction yang dipicu konflik. Setelah topik ini selesai, invarian yang melibatkan lebih dari satu identitas dapat dijaga tanpa kunci manual.
Keterbatasan atom pada invarian antar state#
Atom menjaga satu identitas dengan andal, tetapi tidak dapat menjaga invarian yang mencakup dua identitas sekaligus. Invarian adalah syarat yang harus selalu benar, misalnya total poin dua anggota perpustakaan tetap 65. Pembaruan lewat dua atom membutuhkan dua panggilan swap! yang terpisah, dan thread pembaca dapat menyela di antara keduanya.
; tidak-dijalankan
; Ilustrasi konseptual: dua atom masing-masing aman terhadap thread lain,
; tetapi invarian "total poin selalu 65" tidak terjaga di antara dua
; pembaruan yang terpisah. Pembaca di antara kedua langkah dapat melihat
; total 55 atau 60, yaitu keadaan yang tidak pernah sah.
(def poin-ali (atom 40))
(def poin-budi (atom 25))
; thread A: (swap! poin-ali - 10) lalu (swap! poin-budi + 10)
; thread B: (swap! poin-ali - 5) lalu (swap! poin-budi + 5)Keadaan di antara dua pembaruan itu sah menurut masing-masing atom, tetapi melanggar invarian keseluruhan. Keadaan tersebut merupakan race condition yang diperkenalkan pada pembahasan model state. Menyatukan dua atom menjadi satu map pada satu atom menyelesaikan kasus sederhana, tetapi tidak menyelesaikan kasus yang pembaruannya memang terpisah secara alami. Untuk kebutuhan itulah Clojure menyediakan ref dan STM.
Ref dan transaction dosync#
Ref adalah identitas yang nilainya hanya boleh diganti di dalam transaction, yaitu rangkaian operasi yang dijalankan sebagai satu kesatuan atomik. Bentuk transaction pada Clojure adalah dosync, dan STM menjamin seluruh operasi di dalamnya berhasil bersama atau gagal bersama. Apabila kode di luar dosync mencoba mengubah ref, penolakan muncul sebagai exception. Contoh berikut mengurangi stok rak melalui transaction.
(def rak (ref {:judul "Sistem Operasi" :jumlah 12}))
(dosync
(alter rak assoc :jumlah 11))
(println "Setelah transaksi:")
(prn @rak)Setelah transaksi:
{:judul "Sistem Operasi", :jumlah 11}Fungsi alter adalah pengubah ref yang paling umum, dan syaratnya ditegaskan pada contoh berikut, ketika alter dipanggil di luar transaction.
(def saldo (ref 100))
(try
(alter saldo + 1)
(catch IllegalStateException e
(println "Penolakan:" (.getMessage e))))
(println "Saldo tetap:" @saldo)Penolakan: No transaction running
Saldo tetap: 100Pesan No transaction running menyatakan syarat dasarnya secara eksplisit, dan nilai ref tetap utuh karena tidak ada pengubahan yang terjadi. Pembacaan ref dengan @ sah dilakukan di dalam maupun di luar transaction, dan hasilnya selalu satu nilai utuh yang konsisten.
Koordinasi dua ref dengan alter#
Contoh klasik koordinasi dua identitas adalah pemindahan nilai dari satu ref ke ref lain. Fungsi transfer-poin berikut memindahkan poin antara dua anggota perpustakaan di dalam satu transaction. Pemeriksaan saldo dilakukan lebih dulu, kemudian alter mengurangi ref sumber dan menambah ref tujuan. Karena kedua alter berada dalam satu dosync, keduanya terpasang bersama atau tidak sama sekali, sehingga total poin tidak pernah berubah.
(def poin-ali (ref 40))
(def poin-budi (ref 25))
(defn transfer-poin [sumber tujuan jumlah]
(dosync
(when (>= @sumber jumlah)
(alter sumber - jumlah)
(alter tujuan + jumlah))))
(transfer-poin poin-ali poin-budi 10)
(transfer-poin poin-ali poin-budi 5)
(transfer-poin poin-budi poin-ali 3)
(transfer-poin poin-ali poin-budi 100)
(println "Ali:" @poin-ali)
(println "Budi:" @poin-budi)
(println "Total:" (+ @poin-ali @poin-budi))Ali: 28
Budi: 37
Total: 65Empat pemanggilan dijalankan berurutan, termasuk satu pemindahan yang jumlahnya melebihi saldo Ali. Pemindahan terakhir diabaikan oleh when karena syarat saldonya tidak terpenuhi, sedangkan tiga pemindahan pertama terpasang utuh. Invarian total 65 terjaga tanpa satu pun kunci manual karena STM yang mengurus koordinasinya.
Operasi komutatif dengan commute#
Fungsi commute adalah varian alter untuk operasi komutatif, yaitu operasi yang tidak peduli urutan penerapannya, misalnya penambahan pada penghitung. Perbedaannya terletak pada pemeriksaan konflik, karena commute tidak bertentangan dengan operasi commute lain pada ref yang sama, sehingga transaction yang bersaingan tidak saling memaksa pengulangan. Sebagai gantinya, fungsi commute dijalankan sekali lagi pada saat commit, yaitu pemasangan hasil transaction, dengan rangkaian nilai yang sudah final.
(def masuk (ref 0))
(dosync (commute masuk + 1))
(dosync (commute masuk + 1))
(dosync (commute masuk + 1))
(println "Total masuk:" @masuk)Total masuk: 3Tiga transaction berturut-turut menambah penghitung antrean masuk, dan hasilnya 3 sesuai jumlah operasi. Kelebihan commute atas alter muncul pada beban konkurensi nyata, ketika banyak thread menambah penghitung yang sama, karena penggabungan dilakukan sekali di akhir tanpa memaksa transaction lain mengulang.
| Fungsi | Pemeriksaan konflik | Pemanggilan fungsi | Cocok untuk |
|---|---|---|---|
alter | Ketat, konflik memicu retry | Sekali per transaction | Operasi yang peduli nilai antara |
commute | Longgar, tidak berkonflik antar commute | Di transaction dan saat commit | Penambahan dan penggabungan komutatif |
Pemilihan antara keduanya mengikuti makna operasinya. alter dipilih ketika urutan dan nilai antara memengaruhi hasil, sedangkan commute dipilih ketika hanya hasil akhir yang bermakna dan operasinya komutatif.
Kesalahan umum#
Menaruh efek samping di dalam dosync. Transaction yang berkonflik diulang oleh STM, sehingga pencetakan atau penulisan berkas di dalamnya dapat terjadi beberapa kali untuk satu perubahan yang sah. Perbaikannya dengan menjaga isi transaction murni dan memindahkan efek samping ke setelah dosync selesai.
Mengubah ref di luar transaction. Panggilan alter atau ref-set di luar dosync selalu ditolak dengan pesan No transaction running. Perbaikannya dengan membungkus seluruh pengubahan ref dalam satu dosync, sekecil apa pun cakupannya.
Memecah invarian antara atom dan ref. Invarian yang mencakup dua identitas tidak terjaga bila satu sisi dipegang atom dan sisi lainnya dipegang ref, karena keduanya tidak berbagi koordinasi. Perbaikannya dengan memindahkan seluruh identitas yang terlibat invarian ke ref, lalu memperbarui keduanya dalam satu transaction.
Ringkasan#
- Ref adalah identitas yang hanya boleh diubah di dalam transaction
dosyncdan dikoordinasikan oleh STM. altermengubah ref dengan pemeriksaan konflik ketat, sehingga invarian antar ref terjaga dan tidak ada keadaan setengah jalan.commutemenunda penerapan fungsi hingga commit dan cocok untuk operasi komutatif yang tidak peduli urutan.- Transaction yang berkonflik diulang otomatis, sehingga kode di dalam
dosyncwajib bebas efek samping.
Referensi#
- Refs and Transactions — Clojure
— Dokumentasi resmi ref,
dosync,alter, dancommute. - dosync — ClojureDocs — Contoh dan catatan pemakaian transaction dari komunitas.
- commute — ClojureDocs — Contoh operasi komutatif pada ref dari komunitas.