Sejak Crystal 1.21, fiber tidak lagi berjalan pada satu thread yang tetap, melainkan di dalam execution context, yaitu konteks eksekusi yang mengelola sekumpulan penjadwal tempat fiber-fiber dijalankan. Topik ini membahas konteks default yang menyertai setiap program, konteks paralel Fiber::ExecutionContext::Parallel, konteks konkuren Fiber::ExecutionContext::Concurrent, cara memperbesar paralelisme, serta perubahan perilaku same_thread yang menyertai fitur ini. Setiap program dijalankan dan keluaran aslinya dicantumkan apa adanya.
Konteks default setiap program#
Setiap program Crystal otomatis memulai satu execution context yang diperlihatkan sebagai Fiber::ExecutionContext.default, dan fiber utama berjalan di dalamnya. Konteks ini bertipe Parallel, tetapi dibuat dengan kapasitas satu demi kesesuaian perilaku dengan Crystal versi lama yang berjalan pada satu thread. Metode spawn tanpa argumen konteks menjadwalkan fiber baru ke konteks milik fiber saat ini, sehingga seluruh spawn pada contoh topik-topik sebelumnya sesungguhnya menjadwalkan fiber ke konteks default ini.
konteks = Fiber::ExecutionContext.default
puts konteks.class
puts "kapasitas: #{konteks.capacity}"
puts "nama: #{konteks.name}"# menjalankan program konteks default
crystal run ec-default.crFiber::ExecutionContext::Parallel
kapasitas: 1
nama: DEFAULTMetode capacity melaporkan jumlah penjadwal maksimum yang boleh dijalankan konteks, sedangkan nama DEFAULT menandai konteks bawaan runtime. Kapasitas satu berarti pekerjaan yang dijadwalkan dengan spawn biasa berjalan satu per satu, sekalipun fiber-fibernya bergantian secara aktif. Konteks default dapat diperbesar pada saat berjalan, dan pembesarannya dibahas pada bagian akhir topik ini.
Konteks paralel dengan Parallel#
Kelas Fiber::ExecutionContext::Parallel membuat konteks yang beranggotakan beberapa penjadwal, dan setiap penjadwal berjalan pada satu thread sistem operasi. Parameternya adalah nama konteks dan batas atas paralelismenya, misalnya Parallel.new("pengolah", 2) untuk konteks yang boleh menjalankan paling banyak dua fiber secara serempak. Paralelisme aktualnya dinamis, yaitu bertambah ketika antrean fiber penuh dan menyusut ketika kosong, tetapi tidak pernah melewati batas yang ditetapkan.
ec = Fiber::ExecutionContext::Parallel.new("pengolah", 2)
puts "kapasitas konteks: #{ec.capacity}"
selesai = Channel(Nil).new
4.times do
ec.spawn do
selesai.send(nil)
end
end
4.times { selesai.receive }
puts "empat tugas paralel selesai"# menjalankan program konteks paralel
crystal run ec-parallel.crkapasitas konteks: 2
empat tugas paralel selesaiPemanggilan ec.spawn menjadwalkan fiber ke konteks ec, bukan ke konteks default, sehingga paling banyak dua fiber di antara keempatnya berjalan secara serempak. Fiber pada konteks paralel tidak terikat pada satu thread, dan fiber yang digantungkan boleh dilanjutkan pada thread lain oleh penjadwalnya. Pola channel selesai digunakan kembali di sini agar program utama hanya berhenti setelah keempat fiber melapor, sebab urutan penyelesaian fiber pada konteks paralel tidak dapat dipastikan.
Konteks konkuren dengan Concurrent#
Kelas Fiber::ExecutionContext::Concurrent membuat konteks yang seluruh fibernya berbagi satu penjadwal, sehingga fiber-fibernya bergantian secara kooperatif dan tidak pernah berjalan paralel satu sama lain. Konteks ini tetap berjalan paralel terhadap konteks lain, termasuk konteks default. Karena tidak ada dua fiber yang aktif bersamaan, data yang dibagi antar fiber dalam konteks yang sama tidak memerlukan perlindungan khusus, dan sinkronisasinya menjadi lebih murah.
ec = Fiber::ExecutionContext::Concurrent.new("pengolah")
hasil = Channel({Char, Array(String)}).new
ec.spawn do
baris = [] of String
3.times { |i| baris << "tugas A langkah #{i}" }
hasil.send({'A', baris})
end
ec.spawn do
baris = [] of String
3.times { |i| baris << "tugas B langkah #{i}" }
hasil.send({'B', baris})
end
kumpulan = Hash(Char, Array(String)).new
2.times do
huruf, baris = hasil.receive
kumpulan[huruf] = baris
end
['A', 'B'].each do |huruf|
kumpulan[huruf].each { |b| puts b }
end
puts "dua fiber konkuren selesai"# menjalankan program konteks konkuren
crystal run ec-concurrent.crtugas A langkah 0
tugas A langkah 1
tugas A langkah 2
tugas B langkah 0
tugas B langkah 1
tugas B langkah 2
dua fiber konkuren selesaiSetiap fiber mengumpulkan hasilnya sendiri ke dalam array, mengirimkannya melalui channel, dan program utama mencetak hasil dalam urutan yang ditetapkannya sendiri. Pola ini menjaga keluaran tetap sama di setiap eksekusi meskipun urutan penjadwalan kedua fiber tidak dijamin. Konsekuensi berbagi satu penjadwal perlu diwaspadai, yaitu satu fiber yang memblokir tanpa titik gantung menghentikan seluruh fiber dalam konteks tersebut, sehingga pekerjaan berat sebaiknya dipindahkan ke konteks paralel.
Memperbesar paralelisme konteks default#
Nilai acuan jumlah pekerja konteks default dapat dibaca melalui metode kelas Fiber::ExecutionContext.default_workers_count. Metode ini menghormati variabel lingkungan CRYSTAL_WORKERS apabila diatur, dan apabila tidak, mengambil jumlah CPU logis yang tersedia. Nilai tersebut tidak otomatis diterapkan, sehingga pembesaran kapasitas dilakukan secara eksplisit melalui metode resize.
puts "jumlah pekerja yang disarankan: #{Fiber::ExecutionContext.default_workers_count}"
puts "kapasitas konteks default: #{Fiber::ExecutionContext.default.capacity}"# menjalankan program dengan dua pekerja pada variabel lingkungan
CRYSTAL_WORKERS=2 crystal run ec-workers.crjumlah pekerja yang disarankan: 2
kapasitas konteks default: 1konteks = Fiber::ExecutionContext.default
puts "kapasitas awal: #{konteks.capacity}"
konteks.resize(4)
puts "kapasitas baru: #{konteks.capacity}"# menjalankan program pembesaran kapasitas
crystal run ec-resize.crkapasitas awal: 1
kapasitas baru: 4Keluaran pertama memperlihatkan bahwa CRYSTAL_WORKERS=2 membuat default_workers_count melaporkan angka dua, sedangkan kapasitas konteks default tetap satu karena nilai variabel lingkungan tidak diterapkan secara otomatis. Pemisahan keduanya menegaskan pembagian peran, yaitu variabel lingkungan memberi nilai acuan, sedangkan keputusan pemakaiannya tetap berada di tangan program. Metode resize(4) pada program kedua memperbesar konteks default saat program berjalan, dan capacity langsung melaporkan nilai barunya. Setelah kapasitas diperbesar, fiber yang dijadwalkan dengan spawn biasa pun dapat berjalan serempak sampai batas tersebut.
same_thread tidak lagi didukung#
Sebelum Crystal 1.21, argumen same_thread: true pada spawn menjamin fiber berjalan pada thread yang sama dengan pembuatnya. Jaminan itu tidak dapat dipertahankan ketika fiber bebas berpindah thread antar penggantungan, sehingga argumen tersebut kini menimbulkan peringatan pengabaian pada saat kompilasi dan error RuntimeError tepat pada pemanggilan spawn di konteks paralel. Penolakan berlapis ini membuat kode yang mengandalkan same_thread terdeteksi sejak dini, bukan berjalan dengan perilaku yang berubah diam-diam.
begin
spawn(same_thread: true) do
puts "tidak sampai dieksekusi"
end
rescue error : RuntimeError
puts "spawn ditolak: #{error.message}"
end# menjalankan program penolakan same_thread
crystal run ec-same-thread.crIn ec-same-thread.cr:2:22
2 | spawn(same_thread: true) do
^
Warning: Deprecated argument same_thread. It will have no effect with execution contexts.
A total of 1 warnings were found.
spawn ditolak: Fiber::ExecutionContext::Parallel::Scheduler#spawn doesn't support same_thread:trueKeluaran di atas memperlihatkan dua lapisan perlindungan sekaligus, yaitu peringatan pengabaian dari compiler dan penolakan berupa error yang dapat ditangkap dengan rescue. Blok fiber pada program itu tidak pernah dijalankan, sehingga baris tidak sampai dieksekusi tidak pernah tercetak. Kode yang masih mengandalkan same_thread perlu ditinjau ulang, dan kelas Fiber::ExecutionContext::Isolated tersedia sebagai alternatif untuk fiber tunggal yang menguasai satu thread penuh selama masa hidupnya.
Kesalahan umum#
Tiga kesalahan berikut lazim muncul ketika fitur execution context baru pertama kali digunakan.
- Mengira
spawnbiasa sudah berjalan paralel. Fiber pada konteks default berbagi kapasitas satu, sehingga tugas berat yang dipecah denganspawntetap berjalan satu per satu. Perbaikannya, buat konteksParalleldengan kapasitas yang diinginkan atau perbesar konteks default denganresize. - Menaruh pekerjaan panjang di konteks
Concurrent. Seluruh fiber konteks itu menunggu satu fiber yang tidak pernah menyerahkan giliran. Perbaikannya, pindahkan pekerjaan tersebut ke konteksParallelatau sisipkanFiber.yieldagar giliran berpindah. - Menganggap nilai
capacitysebagai jumlah thread yang sedang aktif.capacityadalah batas atas paralelisme, sedangkan jumlah penjadwal yang benar-benar aktif naik dan turun mengikuti beban. Gunakan nilai itu sebagai batas desain, bukan sebagai penghitung thread.
Ringkasan#
- Sejak Crystal 1.21, fiber berjalan di dalam execution context, dan
spawntanpa argumen menjadwalkan fiber ke konteks milik fiber saat ini. - Konteks default bertipe
Paralleldengan kapasitas satu, dan kelasConcurrentmenyediakan konteks satu thread yang kooperatif. - Kelas
Parallel.new("nama", batas)membuka paralelisme hingga batas yang ditetapkan, dengan jumlah penjadwal aktif yang dinamis. - Jumlah pekerja acuan konteks default dibaca melalui
default_workers_countyang menghormatiCRYSTAL_WORKERS, sedangkan pembesaran aktual menggunakanresize. - Argumen
same_thread: truesudah tidak didukung dan memicu peringatan kompilasi serta error pada saat penjadwalan.
Referensi#
- Concurrency guide — Crystal — panduan resmi konkurensi Crystal yang menjelaskan model fiber dan channel.
- Fiber::ExecutionContext — Crystal API
— dokumentasi modul execution context, termasuk
defaultdandefault_workers_count. - Fiber::ExecutionContext::Parallel — Crystal API
— dokumentasi konteks paralel, termasuk
capacitydanresize.