Fiber adalah unit konkurensi paling dasar di Crystal, yaitu unit eksekusi ringan yang dikelola oleh runtime bahasa. Topik ini membahas cara spawn menjadwalkan fiber, sifat penjadwalan kooperatifnya, cara memberi nama fiber, dan pola penungguan berbasis channel agar program utama tidak berhenti sebelum fiber selesai. Setiap perilaku diilustrasikan dengan program yang dapat dijalankan beserta keluaran aslinya.
Fiber sebagai green thread#
Fiber adalah green thread, yaitu unit eksekusi yang dikelola oleh runtime Crystal sendiri, bukan oleh penjadwal sistem operasi. Satu thread sistem operasi dapat membawa banyak fiber, sehingga membuat fiber jauh lebih murah daripada membuat thread baru. Program dapat memuat ribuan fiber sekaligus tanpa membebani sistem operasi, dan biaya pergantian antar fiber juga jauh lebih kecil daripada pergantian thread. Berbeda dengan thread sistem operasi yang dapat dipotong kapan saja oleh penjadwalnya, pergantian fiber sepenuhnya berada di tangan runtime Crystal.
Fiber bekerja secara kooperatif. Setiap fiber berjalan terus sampai mencapai titik gantung (suspension point), yaitu posisi ketika fiber menyerahkan giliran kepada fiber lain. Titik gantung yang lazim ditemui adalah sleep, operasi I/O, dan Channel#receive. Fiber tidak pernah dipotong paksa di tengah perhitungan, sehingga kode di antara dua titik gantung selalu berjalan utuh tanpa disisipi fiber lain. Sifat ini menjelaskan hampir semua perilaku pada bagian berikut, mulai dari program yang berhenti terlalu dini sampai urutan keluaran yang mudah diprediksi.
Menjadwalkan fiber dengan spawn#
Metode spawn menjadwalkan fiber baru untuk dijalankan dan langsung mengembalikan kendali ke pemanggilnya. Blok yang menyertainya menjadi isi fiber tersebut, sedangkan kode setelah spawn tetap berjalan tanpa menunggu fiber selesai. Metode ini mengembalikan objek Fiber yang mewakili fiber baru, sehingga fiber dapat disimpan ke dalam variabel untuk keperluan tertentu. Parameter opsional seperti name juga diterima pada saat pemanggilan dan dibahas pada bagian tersendiri. Program berikut menjadwalkan satu fiber latar yang tidur selama 200 milidetik sebelum mencetak satu baris.
spawn do
sleep 200.milliseconds
puts "tugas latar selesai"
end
puts "program utama berhenti"# menjalankan program fiber
crystal run tanpa-tunggu.crprogram utama berhentiKeluaran hanya memuat satu baris dari program utama. Fiber latar baru saja dijadwalkan ketika program utama mencetak barisnya dan berhenti, sedangkan berhentinya program utama mengakhiri seluruh proses beserta semua fiber yang belum selesai. Fiber pada program di atas bahkan tidak pernah mencapai puts-nya karena masih menunggu sleep ketika proses berakhir. spawn memang tidak menyediakan penungguan otomatis, sehingga program memerlukan mekanisme tersendiri apabila hasil fiber harus tetap diperoleh, dan mekanisme itu dibahas pada bagian akhir topik ini.
Penjadwalan kooperatif#
Sifat kooperatif membuat urutan berjalannya dua fiber mudah diprediksi selama tidak ada titik gantung di antaranya. Dua fiber berikut masing-masing mencetak tiga langkah tanpa pernah menyerahkan giliran.
spawn do
3.times { |i| puts "tugas A langkah #{i}" }
end
spawn do
3.times { |i| puts "tugas B langkah #{i}" }
end
sleep 10.milliseconds# menjalankan program dua fiber tanpa titik gantung
crystal run tanpa-titik-gantung.crtugas A langkah 0
tugas A langkah 1
tugas A langkah 2
tugas B langkah 0
tugas B langkah 1
tugas B langkah 2Seluruh langkah tugas A ditampilkan lebih dulu, baru seluruh langkah tugas B. Fiber A berjalan sampai selesai karena tidak pernah mencapai titik gantung, dan fiber B baru mendapat giliran setelah fiber A berakhir. Urutan A lebih dulu mengikuti urutan pemanggilan spawn, bukan kebetulan waktu. Tidak ada pemotongan paksa di tengah perhitungan seperti yang terjadi pada thread sistem operasi.
Metode kelas Fiber.yield memaksa fiber yang sedang berjalan menyerahkan giliran dan masuk kembali ke antrean penjadwal. Dengan menyisipkan Fiber.yield di antara langkah-langkahnya, dua fiber dapat bergantian secara teratur tanpa menunggu I/O.
spawn do
3.times do |i|
puts "fiber: langkah #{i}"
Fiber.yield
end
end
3.times do |i|
puts "utama: langkah #{i}"
Fiber.yield
end# menjalankan program pergantian giliran dengan Fiber.yield
crystal run giliran-fiber.crutama: langkah 0
fiber: langkah 0
utama: langkah 1
fiber: langkah 1
utama: langkah 2
fiber: langkah 2Keluaran kini berselang-seling antara fiber utama dan fiber yang dijadwalkan. Setiap Fiber.yield mengembalikan fiber pemanggil ke antrean, sehingga penjadwal memilih fiber lain untuk berjalan dan giliran berpindah. Fiber utama pun dapat memanggil Fiber.yield karena penjadwal memperlakukannya sebagai salah satu fiber biasa.
Menamai fiber#
Parameter name pada spawn memberi nama pada fiber yang dibuat. Nama membantu saat membaca log atau menelusuri perilaku program karena setiap keluaran dapat dikaitkan dengan fiber asalnya. Pada program dengan banyak fiber yang menjalankan tugas serupa, nama membuat keluaran dapat dipilah tanpa menambah struktur data tambahan. Di dalam blok fiber, metode kelas Fiber.current mengembalikan objek fiber yang sedang berjalan, sehingga nama fiber dibaca melalui Fiber.current.name.
spawn name: "tugas-laporan" do
puts "fiber berjalan dengan nama #{Fiber.current.name}"
end
sleep 10.milliseconds# menjalankan program fiber bernama
crystal run fiber-nama.crfiber berjalan dengan nama tugas-laporanNama bersifat opsional. Fiber yang tidak diberi nama melaporkan nil sebagai namanya, dan penamaan hanya dapat dilakukan pada saat fiber dibuat melalui parameter name. Objek yang dikembalikan spawn menyimpan nama yang sama, sehingga nama dapat dibaca langsung dari objek fiber tersebut. Penamaan tidak mengubah perilaku penjadwalan sama sekali, sehingga fiber bernama dan fiber anonim diperlakukan identik. sleep di akhir program menjaga program utama tetap hidup sampai fiber selesai mencetak, yaitu penungguan paling sederhana yang disempurnakan pada bagian berikutnya.
Menunggu fiber selesai dengan channel#
Karena spawn tidak menunggu, program memerlukan mekanisme tersendiri agar fiber latar sempat menyelesaikan tugasnya. Pola yang umum menggunakan channel, yaitu jalur kirim-terima nilai antar fiber. Channel untuk keperluan ini biasa dinamai done. Setiap fiber mengirim satu nilai ke channel tersebut ketika tugasnya selesai, sedangkan program utama menerima sebanyak jumlah fiber. Penerimaan yang memblokir inilah yang menjaga program tetap hidup sampai semua fiber selesai.
done = Channel(Nil).new
4.times do
spawn do
sleep 20.milliseconds
done.send(nil)
end
end
sisa = 3
4.times do
done.receive
puts "satu fiber selesai, tersisa #{sisa}"
sisa -= 1
end
puts "semua fiber selesai"# menjalankan program penunggu empat fiber
crystal run tunggu-done.crsatu fiber selesai, tersisa 3
satu fiber selesai, tersisa 2
satu fiber selesai, tersisa 1
satu fiber selesai, tersisa 0
semua fiber selesaiJumlah penerimaan pada program utama harus sama dengan jumlah fiber yang dijadwalkan. Penerimaan yang kurang membuat beberapa fiber tidak sempat dilaporkan, sedangkan penerimaan berlebih membuat program utama menunggu tanpa akhir. Channel done bertipe Channel(Nil) karena nilainya tidak dibutuhkan, hanya kejadian selesainya yang penting, sedangkan channel bertipe lain dapat membawa hasil kerja setiap fiber, misalnya angka yang dihitung.
Setiap done.receive memblokir program utama sampai satu fiber mengirim, sehingga baris satu fiber selesai tercetak empat kali sesuai jumlah fiber. Setelah empat penerimaan tuntas, program utama mencetak baris terakhir dan berhenti dengan aman. Konsep channel dibahas lebih rinci pada topik tersendiri dalam modul ini.
Kesalahan umum#
Tiga kesalahan berikut lazim muncul ketika fiber baru pertama kali digunakan.
- Program utama berhenti sebelum fiber selesai. Penyebabnya,
spawntidak menunggu dan berhentinya program utama mengakhiri seluruh proses. Perbaikannya, terapkan pola channeldonesehingga program utama hanya berhenti setelah seluruh fiber melapor. - Menganggap fiber berjalan paralel. Seluruh fiber berbagi satu thread dan bergantian hanya pada titik gantung, sehingga pekerjaan perhitungan murni tidak menjadi lebih cepat ketika dibagi ke banyak fiber. Dua fiber pada bagian penjadwalan kooperatif selalu berjalan bergantian dan tidak pernah serempak. Pembagian tugas tetap menguntungkan untuk pekerjaan yang sering menunggu I/O.
- Mengurutkan keluaran dengan
sleep. Memperkirakan lama tidur setiap fiber untuk menentukan urutan cetak adalah balapan melawan penjadwal, dan hasilnya tidak dapat diandalkan. Perbaikannya, kirim hasil melalui channel dan cetak dari satu tempat, misalnya dari fiber utama.
Ringkasan#
- Fiber adalah green thread kooperatif yang dikelola runtime Crystal, dan
spawnmenjadwalkan fiber baru tanpa menunggu fiber selesai. - Fiber hanya bergantian pada titik gantung seperti
sleep, operasi I/O, danChannel#receive, sedangkanFiber.yieldmenyerahkan giliran secara eksplisit. - Parameter
namepadaspawnmenamai fiber, dan nama fiber dibaca melaluiFiber.current.name. - Berhentinya program utama mengakhiri semua fiber, sehingga program menunggu dengan pola channel
doneyang menerima satu nilai dari setiap fiber.
Referensi#
- Concurrency guide — Crystal — panduan resmi yang menjelaskan fiber, spawn, dan channel.
- Fiber — Crystal API
— dokumentasi kelas Fiber, termasuk
Fiber.currentdanFiber.yield.