Episode ini membedah concurrency Ruby: Thread dengan Mutex, Queue, dan race condition, Fiber dengan cooperative scheduling dan Fiber.scheduler, serta Ractor untuk paralelisme sejati dengan API Ractor::Port baru di Ruby 4.0.

Aplikasi modern harus melayani banyak pengguna sekaligus, memproses banyak request, dan tidak membuang waktu menunggu I/O. Di Ruby, concurrency ditangani tiga alat berbeda dengan filosofi berbeda: Thread untuk eksekusi bersamaan dengan sharing state, Fiber untuk kerjasama ringan yang bisa dihentikan-dilanjutkan, dan Ractor untuk paralelisme sejati yang aman dari race condition.
Episode 11 ini membedah ketiganya secara bertahap. Kita mulai dari Thread dengan Mutex dan Queue, lalu Fiber dengan scheduler untuk I/O async, dan diakhiri Ractor beserta API Ractor::Port yang baru di Ruby 4.0. Ingat kembali penjelasan GVL di episode 2: thread Ruby concurrent untuk I/O, tetapi hanya Ractor yang benar-benar parallel untuk CPU.
Thread.new membuat thread baru yang berjalan bersamaan. Method join menunggu thread selesai — tanpanya, program utama bisa berakhir sebelum thread bekerja:
threads = 3.times.map do |i|
Thread.new do
sleep(0.1)
puts "Thread #{i} selesai"
end
end
threads.each(&:join)
puts "Semua selesai"Thread.new do ... end membuat thread yang mengeksekusi block. threads.each(&:join) memanggil join pada setiap thread, memastikan program utama menunggu semuanya. Tanpa join, output Semua selesai bisa muncul lebih dulu. Thread cocok untuk workload yang didominasi menunggu I/O seperti HTTP atau database.
Karena thread berbagi state, dua thread yang mengubah variabel yang sama secara bersamaan bisa menghasilkan nilai salah — inilah race condition. Solusinya adalah Mutex (mutual exclusion) yang memastikan hanya satu thread di satu waktu:
counter = 0
mutex = Mutex.new
threads = 10.times.map do
Thread.new do
10_000.times do
mutex.synchronize { counter += 1 }
end
end
end
threads.each(&:join)
puts counterTanpa mutex.synchronize, hasil counter hampir pasti kurang dari 100.000 karena operasi += tidak atomik. Dengan Mutex, hasilnya tepat 100.000. Monitor adalah variasi yang bisa di-reentrant; Queue dari thread-safe menyediakan antrian aman untuk komunikasi antar thread.
Queue memungkinkan thread berbagi data secara aman: satu thread menulis, yang lain membaca dengan pop yang memblokir sampai data tersedia:
queue = Queue.new
Thread.new { 5.times { |i| queue << i } }
puts queue.pop
puts queue.popqueue << i memasukkan data, dan queue.pop mengeluarkannya. Pemanggilan pop yang kedua menunggu data berikutnya tersedia — tidak ada busy loop yang membuang CPU. Nilai kembalian thread bisa diambil dengan thread.value, yang juga menunggu thread selesai.
Fiber adalah unit eksekusi ringan yang berhenti dan dilanjutkan secara eksplisit — disebut cooperative karena perpindahan kendali diatur oleh programmer, bukan sistem operasi. Fiber.yield menunda fiber, resume melanjutkannya:
fiber = Fiber.new do
puts "mulai"
Fiber.yield "di tengah"
puts "lanjut"
"selesai"
end
puts fiber.resume
puts fiber.resume
puts fiber.resumefiber.resume pertama menjalankan sampai Fiber.yield dan mengembalikan "di tengah". Resume kedua melanjutkan dari titik terakhir dan mengembalikan "selesai". Fiber jauh lebih ringan daripada thread — ribuan fiber bisa hidup dalam satu thread.
Fiber.scheduler adalah interface yang memungkinkan I/O blok (seperti membaca socket) ditangguhkan dan ditangani asinkron dalam satu thread. Scheduler disuntikkan dengan Fiber.set_scheduler, dan library seperti async memanfaatkannya untuk menangani ribuan koneksi konkuren dengan overhead kecil. Inilah rahasia server Ruby berperforma tinggi yang menangani banyak koneksi jaringan.
Ractor (Ruby Actor) mengeksekusi kode secara benar-benar paralel di luar batasan GVL. Keamanannya dijamin oleh isolasi: objek tidak dibagikan antar ractor kecuali dinyatakan shareable. Membuat ractor sama seperti membuat thread, tetapi dengan isolasi data yang ketat:
rakt = Ractor.new { 21 * 2 }
rakt.join
puts rakt.valuerakt.join menunggu raktor selesai, dan rakt.value mengambil hasil perhitungan. Karena tidak ada state yang dibagikan, race condition yang kita lihat di Thread tidak mungkin terjadi di sini. Ractor inilah jawaban Ruby untuk memanfaatkan banyak core CPU.
Ruby 4.0 memperkenalkan Ractor::Port — API komunikasi ractor baru yang menggantikan Ractor.yield dan Ractor.take. Komunikasi kini dipandang sebagai port: raktor berkomunikasi lewat kanal pesan yang eksplisit. Ractor.select memungkinkan satu raktor menunggu pesan dari banyak port sekaligus. Ractor.shareable_proc memastikan proc yang dikirim antar ractor bersifat immutable dan aman dibagikan.
Untuk workload parallel map, kombinasi raktor per item bekerja seperti ini:
data = (1..4).to_a
hasil = data.map do |nilai|
Ractor.new(nilai) { |n| n * n }
end.map(&:value)
puts hasil.inspectRactor.new(nilai) { |n| n * n } membungkus setiap komputasi dalam raktor yang berjalan paralel, lalu map(&:value) mengambil hasil masing-masing. Pada CPU multi-core, empat kuadrat dihitung bersamaan, bukan berurutan. Catatan: membuat terlalu banyak raktor punya overhead, jadi ukur dengan profiling — subjek episode 17.
Warning
Jangan mencampur state yang bisa diubah antar thread tanpa Mutex, dan jangan memindahkan objek non-shareable antar ractor. Kedua kesalahan ini menghasilkan bug yang paling sulit dideteksi — muncul hanya di production dengan beban tinggi.
Episode 11 membuka dunia concurrency Ruby: Thread dengan Mutex, Queue, dan join, Fiber dengan Fiber.yield dan Fiber.scheduler untuk I/O async, serta Ractor untuk paralelisme sejati dengan API Ractor::Port baru di Ruby 4.0.
Inti yang harus dibawa pulang:
Mutex dan Queue.thread.join dan thread.value menunggu dan mengambil hasil thread.resume dan Fiber.yield.Fiber.scheduler memungkinkan I/O asinkron dalam satu thread.Ractor.yield dan Ractor.take.Ractor.select dan Ractor.shareable_proc melengkapi API komunikasi antar ractor.Di episode 12 selanjutnya kita akan membahas RubyGems, Bundler, dan dependency management — gem install dan gem search, struktur gemspec, pembuatan gem dengan bundle gem, Gemfile dan bundle install, Gemfile.lock dengan checksum, source multi-gem, dan kelompok dependency. Di sinilah kalian belajar mengelola library seperti developer profesional.