Topik ini membahas channel sebagai saluran pertukaran pesan bertipe antar thread pada Nim. Pembahasan mencakup semantik antrean dari Channel[T], pola tugas-jawab, yaitu thread utama mengirim pekerjaan dan thread pekerja mengirim kembali hasil, serta pembacaan tanpa menunggu melalui tryRecv. Berbeda dari berbagi variabel langsung, komunikasi lewat channel tidak menimbulkan risiko dua thread menulis ke memori yang sama, karena setiap pesan berpindah utuh dari pengirim ke penerima.
Channel sebagai antrean pesan bertipe#
Channel[T] adalah antrean pesan (message queue) yang menyimpan nilai bertipe T dengan urutan keluar-masuk yang terjaga, yaitu pesan yang dikirim lebih dulu akan diterima lebih dulu. Tipe Channel tersedia dari sistem bawaan Nim ketika dukungan thread aktif, yang pada Nim 2.x merupakan perilaku bawaan kompilasi. Proc send menaruh satu pesan di ekor antrean, sedangkan recv mengambil satu pesan dari kepalanya, dan bila antrean sedang kosong, recv menunggu sampai ada pesan yang datang. Satu pesan hanya diambil oleh satu penerima, sehingga dua thread yang sama-sama membaca satu channel tidak pernah menerima pesan yang sama.
Karena satu pesan hanya diambil satu penerima, channel juga berfungsi sebagai alat pembagi kerja. Sejumlah thread pekerja dapat membaca satu channel tugas yang sama, dan setiap tugas diproses tepat sekali oleh salah satu pekerja tanpa pengunci tambahan. Pemisahan peran ini mengikuti gaya pengiriman pesan (message passing), yaitu thread tidak saling menyentuh memori, melainkan bertukar data melalui antrean yang dikelola sistem. Pola seperti ini menghindarkan program dari persaingan data (data race) karena satu-satunya jalur perpindahan data adalah pesan yang disalin utuh ke penerimanya.
Sebelum dipakai, sebuah channel wajib disiapkan dengan open. Proc open menerima argumen opsional maxItems untuk membatasi kapasitas, sedangkan tanpa argumen, antreannya tidak berbatas. Proc close menandai akhir pemakaian dan melepaskan sumber daya antreannya. Karena Channel[T] adalah tipe nilai yang tidak memakai memori pengelola sampah, objek channel boleh diletakkan sebagai variabel global dan diakses dari proc yang dijalankan di thread lain, misalnya tugas yang diluncurkan dengan spawn dari modul std/threadpool.
# tidak-dijalankan
var kanal: Channel[string]
kanal.open()
kanal.send("pesan pertama")
let isi = kanal.recv()
kanal.close()Pola tugas-jawab antar thread#
Pola yang paling sering dipakai adalah tugas-jawab (request-response). Thread utama mengirim serangkaian permintaan ke satu channel, thread pekerja membaca permintaan itu dalam perulangan, mengolahnya, lalu mengirim jawaban ke channel kedua. Pekerjaan dihentikan dengan pesan penanda (sentinel), yaitu nilai khusus yang disepakati bermakna “tidak ada pekerjaan lagi”. Setelah thread utama menunggu pekerja selesai, baru channel jawaban dikosongkan. Urutan pengerjaan ini menegakkan determinisme: seluruh jawaban sudah pasti berada di antrean pada saat pembacaan dimulai.
import std/threadpool
type
Jawaban = tuple
id: int
nilai: int
var kotakMasuk: Channel[int]
var kotakJawab: Channel[Jawaban]
proc penghitungKuadrat(): int =
while true:
let pesan = kotakMasuk.recv()
if pesan < 0:
return 0
kotakJawab.send((id: pesan, nilai: pesan * pesan))
kotakMasuk.open()
kotakJawab.open()
discard spawn penghitungKuadrat()
for i in 1 .. 4:
kotakMasuk.send(i)
kotakMasuk.send(-1)
sync()
var hasil: array[4, int]
while true:
let (ada, pesan) = kotakJawab.tryRecv()
if not ada:
break
hasil[pesan.id - 1] = pesan.nilai
kotakMasuk.close()
kotakJawab.close()
for i in 0 .. 3:
echo "kuadrat ", i + 1, " = ", hasil[i]# kompilasi program tugas-jawab channel
nim c --warning[Deprecated]:off --hints:off kanal_kuadrat.nim
# jalankan program hasil kompilasi
./kanal_kuadratkuadrat 1 = 1
kuadrat 2 = 4
kuadrat 3 = 9
kuadrat 4 = 16Program di atas mengirim angka 1 sampai 4 ditambah penanda -1, dan pekerja menjawab dengan tuple (id, nilai) berisi identitas permintaan beserta kuadratnya. Jawaban tidak dicetak begitu diterima, melainkan ditempatkan ke elemen hasil[pesan.id - 1], sehingga urutan cetak mengikuti id, bukan urutan kedatangan pesan. Penempatan berdasarkan identitas seperti ini menjaga keluaran tetap sama pada setiap eksekusi walaupun kecepatan tiap thread berubah-ubah. sync memastikan pekerja sudah berhenti sebelum antrean jawaban dikosongkan, sehingga perulangan tryRecv menemukan tepat empat jawaban.
Membaca tanpa menunggu dengan tryRecv#
recv yang menunggu tidak selalu diinginkan, misalnya ketika thread utama perlu tetap bekerja sambil sesekali memeriksa antrean. Untuk kebutuhan itu, tryRecv mencoba mengambil satu pesan tanpa pernah menunggu. Nilai kembaliannya adalah tuple dengan dua bidang, yaitu dataAvailable yang bernilai true bila ada pesan yang berhasil diambil, dan msg yang berisi pesan itu. Bila antrean kosong, dataAvailable bernilai false dan msg berisi nilai default tipe pesan, sehingga bidang msg hanya boleh dipercaya setelah dataAvailable diperiksa.
# tidak-dijalankan
let (ada, pesan) = kotakJawab.tryRecv()
if ada:
echo "diterima: ", pesan
else:
echo "antrean kosong"Pola perulangan while true dengan tryRecv pada contoh program memakai tuple ini sebagai alat pengosongan (drain), yaitu mengambil semua pesan yang tersisa sampai antrean benar-benar kosong. Pola serupa dapat dipakai untuk memeriksa jawaban secara berkala di tengah pekerjaan lain, asalkan pesan yang belum diambil tetap tersimpan di antrean karena channel tidak membuang isi antreannya sendiri. Pengosongan lazim dilakukan setelah sync karena pada saat itu seluruh jawaban sudah masuk antrean, sehingga perulangan cukup berhenti pada percobaan pertama yang mengembalikan dataAvailable bernilai false, dan tidak ada pesan yang tertinggal tanpa diproses.
Kesalahan umum#
Tiga kesalahan berikut paling sering muncul ketika channel baru dipraktikkan.
- Lupa memanggil
opensebelum channel dipakai. Tanpaopen, perilakusenddanrecvtidak terdefinisi dengan baik dan program bisa menggantung. Siapkanopenberdampingan dengan deklarasi pemakaiannya. - Menganggap channel berbagi memori dengan pengirimnya.
sendmenyalin nilai pesan ke antrean, sehingga perubahan pada variabel asal setelah pengiriman tidak terlihat oleh penerima. Rancang pesan sebagai nilai yang berdiri sendiri. - Menunggu pesan yang tidak pernah dikirim.
recvpada antrean kosong menunggu tanpa batas, dan kelalaian satu sisi membuat pasangannya menggantung selamanya. Gunakan penanda akhir seperti nilai -1 pada contoh, atau alihkan ketryRecvbila ketiadaan pesan adalah keadaan yang wajar.
Ringkasan#
Channel[T]adalah antrean pesan bertipe dengan urutan keluar-masuk yang terjaga, wajib disiapkan denganopen, dan diakhiri denganclose.sendmenyalin pesan ke ekor antrean,recvmengambil dari kepala dan menunggu bila kosong, sedangkantryRecvmengembalikan tuple(dataAvailable, msg)tanpa pernah menunggu.- Pola tugas-jawab yang deterministik memakai pesan penanda untuk menghentikan pekerja, menunggu dengan
sync, lalu mengosongkan antrean jawaban dengan penempatan berdasarkan identitas pesan.
Referensi#
- threadpool — Nim documentation
— Kontrak
spawndansyncyang dipakai untuk meluncurkan thread pekerja serta menunggunya pada contoh tugas-jawab.