- Bahasa Zig memperkenalkan model baru berbasis antarmuka
Iountuk mengurangi kompleksitas desain I/O asinkron yang ada - Model ini mempertahankan struktur fungsi yang sama tanpa membedakan kode sinkron dan asinkron, serta menyediakan dua implementasi:
Io.ThreadeddanIo.Evented Io.Threadedpada dasarnya menjalankan eksekusi sinkron, sedangkanIo.Eventedmelakukan eksekusi asinkron berbasis event loop- Pengembang dapat mengontrol eksekusi paralel melalui fungsi
async()danconcurrent(), sehingga optimasi performa dimungkinkan tanpa mengubah kode - Pendekatan ini menyelesaikan masalah function coloring sambil tetap mempertahankan kesederhanaan dan kemampuan kontrol khas Zig untuk meraih performa asinkron
Perubahan Desain Asinkron di Zig
- Zig mencari pendekatan baru karena desain asinkron sebelumnya kurang selaras dengan filosofi minimalisme bahasa tersebut
- Desain lama memiliki tingkat integrasi yang rendah dengan fitur lain
- Model baru memungkinkan I/O sinkron dan asinkron ditangani dengan struktur kode yang sama
- Desain baru berpusat pada antarmuka generik
Io- Semua fungsi I/O dijalankan dengan menerima instance
Iosebagai parameter - Strukturnya mirip dengan antarmuka
Allocator, sehingga I/O dapat dikendalikan dengan cara yang serupa seperti alokasi memori
- Semua fungsi I/O dijalankan dengan menerima instance
Struktur Antarmuka Io
- Pustaka standar menyertakan dua implementasi dasar
Io.Threaded: pada dasarnya eksekusi sinkron, dengan pemrosesan paralel berbasis thread bila diperlukanIo.Evented: eksekusi asinkron berbasis event loop (menggunakanio_uring,kqueue, dan lain-lain)
- Pengguna juga dapat menulis implementasi
Iobaru sendiri, sehingga bisa mengontrol cara eksekusi dengan lebih rinci
Contoh Kode dan Cara Kerjanya
- Fungsi contoh
saveFile()membuat file, menulis isinya, lalu menutupnya- Saat menggunakan
Io.Threaded, fungsi ini berjalan lewat system call biasa - Saat menggunakan
Io.Evented, fungsi ini dijalankan melalui backend asinkron - Dalam kedua kasus, penyelesaian pekerjaan dijamin pada saat pemanggilan
writeAll()
- Saat menggunakan
- Kode yang sama berjalan identik baik di lingkungan sinkron maupun asinkron
- Penulis library tidak perlu memikirkan metode eksekusinya
Eksekusi Paralel dan async() / concurrent()
- Fungsi
async()meminta eksekusi asinkron, tetapi padaIo.Threadedbisa saja dijalankan segera- Pada
Io.Evented, dua file benar-benar dapat disimpan secara bersamaan lewat eksekusi asinkron
- Pada
- Fungsi
concurrent()digunakan ketika eksekusi paralel yang nyata memang diperlukanIo.Threadedmemanfaatkan thread poolIo.Eventedmenanganinya dengan cara yang sama sepertiasync()
- Pemilihan fungsi yang salah (
asyncalih-alihconcurrent) dianggap sebagai bug, dan tidak bisa dicegah di tingkat bahasa
Gaya Kode dan Integrasi Bahasa
- Gaya kode Zig biasa tetap dipertahankan tanpa sintaks khusus untuk asinkron
- Sintaks alur kontrol yang ada seperti
try,defer, dan lainnya tetap digunakan apa adanya - Andrew Kelley menyebutnya “terbaca seperti kode Zig standar”
- Sintaks alur kontrol yang ada seperti
- Sebagai contoh, ditunjukkan implementasi lookup DNS asinkron
- Tidak seperti
getaddrinfo(), implementasi ini hanya mengembalikan respons sukses pertama dan membatalkan permintaan lainnya
- Tidak seperti
Rencana Ke Depan dan Status Pengembangan
Io.Eventedmasih berada pada tahap eksperimental, dan belum mendukung sebagian OS- Implementasi
Ioyang kompatibel dengan WebAssembly sedang direncanakan dan memerlukan pengembangan fitur terkait - Ada 24 item pekerjaan lanjutan terkait
Io, dan sebagian besar masih belum selesai - Zig masih belum mencapai versi 1.0, dan I/O asinkron serta pembuatan kode native adalah tugas besar yang masih tersisa
- Dengan desain ini, diharapkan frekuensi penulisan ulang kode akibat perubahan antarmuka I/O akan berkurang
Ringkasan Diskusi Komunitas
- Sejumlah komentar menilai pendekatan Zig lebih sederhana dan fleksibel dibanding model async/await Rust
- Rust menjadi lebih kompleks ketika beberapa executor digunakan bersamaan
- Zig melalui antarmuka
Iomembuka kemungkinan koeksistensi banyak executor
- Sebagian pihak menyoroti bahwa kodenya bisa menjadi agak lebih bertele-tele
- Namun, desain API yang eksplisit meningkatkan kemampuan kontrol atas keamanan, performa, dan pengujian
- Diskusi teknis juga berlanjut mengenai perbedaan eksekusi asinkron dan berbasis thread, serta pendekatan implementasi stackful vs stackless coroutine
Iodi Zig diimplementasikan sebagai perluasan pustaka standar tanpa perlakuan khusus di tingkat bahasa- Ke depan, fitur stackless coroutine akan ditambahkan
Kesimpulan
- Model asinkron baru Zig bertujuan menyeimbangkan kesederhanaan bahasa dan I/O berperforma tinggi
- Dengan menyelesaikan masalah function coloring, menyatukan kode sinkron dan asinkron, serta menyediakan struktur kontrol yang eksplisit, ini dinilai sebagai langkah kunci menuju stabilisasi Zig 1.0
1 komentar
Komentar Hacker News
Secara keseluruhan, tulisan ini akurat dan diteliti dengan baik
Hanya saja ada beberapa koreksi kecil.
Pada instance
Io.Threaded,async()sebenarnya tidak berjalan secara asinkron dan dieksekusi langsung. Namunstd.Io.Threadedsecara default menggunakan thread pool untuk mendistribusikan pekerjaan asinkron.Tetapi, jika diinisialisasi dengan
init_single_threaded, perilakunya akan sesuai dengan yang dijelaskan dalam artikel.Satu lagi, dulu ada fungsi bernama
asyncConcurrent(), tetapi sekarang namanya sudah diubah menjadiconcurrent()Jika ingin memberi umpan balik di masa mendatang, kirim email ke lwn@lwn.net.
Terima kasih atas usulan perbaikannya dan atas pekerjaan terkait Zig
Saya penasaran bug seperti apa yang bisa muncul jika
asyncConcurrent()keliru dipakai di tempat yang seharusnya menggunakanasync().Saya ingin tahu apakah, tergantung model IO-nya, ini bisa menjadi UB (undefined behavior), atau hanya sekadar kesalahan logika
concurrent()adalah ia meningkatkan keterbacaan dan daya ekspresif kode, sehingga jelas menunjukkan, “kode ini memang harus dijalankan secara paralel”Menurut saya desain ini cukup masuk akal.
Namun penjelasan Zig membingungkan.
Mereka menekankan bahwa masalah function coloring sudah diselesaikan, tetapi pada dasarnya mereka hanya mendorong IO ke dalam effect type.
Ini tetap mengharuskan pemanggil mempertahankan token, jadi masih merupakan bentuk pewarnaan juga.
Menurut saya ini mirip dengan pendekatan penanganan async di Go
Model async-await Zig yang lama juga sebenarnya sudah menyelesaikan masalah coloring.
Karena compiler otomatis membuat versi sinkron/asinkron sesuai konteks pemanggilan
Zig menyelesaikannya secara praktis dengan dependency injection, dan itu sudah cukup memadai.
Kompleksitas pemanggilan async memang tidak bisa dihindari, tetapi itu bagian yang tak terelakkan jika ingin kontrol yang presisi
Anda bisa mendeklarasikan variabel io global dan memakainya dari mana saja (meskipun tentu tidak disarankan saat menulis library).
Jika melihat tulisan What color is your function? yang merangkum lima syarat masalah function coloring, kemungkinan besar pendekatan Zig tidak memenuhi sebagian syarat itu (terutama 4 dan 5)
Namun pendekatan seperti ini bisa memicu masalah seperti deadlock.
Sebagian kode tidak thread-safe, jadi coloring justru membantu
Desain ini tampak sangat mirip dengan async di Scala.
Di Scala, execution context diteruskan sebagai parameter implisit, sedangkan Zig menerimanya secara eksplisit.
Dalam praktiknya, ini tidak jauh lebih baik daripada memakai thread dan queue secara langsung, dan pengelolaan execution context menimbulkan kompleksitas serta perilaku yang sulit diprediksi.
Tim Zig tampaknya kurang memiliki pengalaman Scala sehingga menganggap pendekatan ini baru
JVM mengatasinya dengan virtual thread, tetapi bahasa tingkat rendah sulit mencapai efisiensi yang sama.
Karena itu bahasa seperti Zig memerlukan solusi skalabilitas dengan pendekatan berbeda
Pada sistem async/await Zig yang lama, fungsi bisa melakukan suspend/resume.
Dengan fitur ini saya ingin mencoba mengimplementasikan suspend/resume frame berbasis interrupt perangkat saat mengembangkan OS.
Di sistem io baru, tampaknya saya harus mengimplementasikannya sendiri, dan itu agak disayangkan
@asyncSuspenddan@asyncResume.Io baru adalah abstraksi umum untuk mode sinkron, threaded, dan event-based, jadi mekanisme suspend tidak disertakan
Jika melihat prototipe Io.Evented saat ini, ini juga bisa ditangani di library pihak ketiga berbasis stackless coroutine
Pada kode contoh dikatakan bahwa saat
writeAll()mengembalikan nilai, pekerjaan sudah selesai,tetapi implementasi IO bisa beragam, jadi secara nyata penyelesaian seharusnya dijamin saat
defermulai.Jika tidak, perlu ada pelacakan dependensi antara
createFiledanwriteAll.Kalau begitu, pada akhirnya ini tampak tidak berbeda dari pemanggilan blocking.
Selain itu, juga tidak jelas mengapa antarmuka ini dinamai IO.
Pada praktiknya ini lebih dekat ke abstraksi “menjalankan sesuatu di konteks lain”
Dokumen terkait: std.Io
Contoh berikut menarik
Di Rust atau Python, coroutine tidak akan berjalan jika tidak di-await.
Sebaliknya, jika dalam contoh Zig
io.asyncbergerak sendiri, maka ini mirip dengan pembuatan task.Ini adalah desain yang valid, tetapi bukan arah yang dipilih bahasa lain
asyncberjalan di thread pemanggil sampai sebelum yield.await(io)dipanggil.Apakah ia langsung berjalan atau dimasukkan ke antrean thread pool bergantung pada implementasi runtime Io
await.Dalam evented io, kedua pekerjaan bisa dieksekusi secara selang-seling (interleaved), dan dalam threaded io bisa berjalan di latar belakang.
Jadi tidak ada “task yang diam-diam berjalan entah di mana”
Sebagai orang yang memakai Go setiap hari, saya merasa Io Zig memperbaiki berbagai kekurangan di Go.
Namun saya penasaran apakah Zig punya konsep channel.
Di Go ada keyword select, tetapi saya selalu merasa sayang karena itu tidak bisa dipakai pada socket
Channel di Go punya overhead puluhan siklus, jadi tidak efisien untuk IO berukuran kecil.
Sebaliknya, itu berguna untuk pemindahan data berukuran besar atau sinkronisasi many-to-many
std.Io.Queueyang mirip dengan channel di Go.Pernyataan select juga bisa diimplementasikan secara serupa, meskipun secara sintaksis kurang ergonomis.
Sebagai gantinya, ini bisa berjalan di berbagai runtime IO tanpa GC
Menurut saya pendekatan Zig yang “tanpa warna” jauh lebih baik
Goroutine hanyalah green thread, dan channel hanyalah queue yang thread-safe, dan Zig juga sudah menyediakannya di pustaka standar
Versi async dari Io di Zig tampak hampir sama dengan pendekatan Go.
Bedanya, di Go biaya alokasi stack saat memanggil library C besar, dan syscall langsung punya masalah kompatibilitas lintas platform.
Zig tampaknya membuat ini bisa dikonfigurasi, sehingga berbagai trade-off bisa dipilih tanpa mengubah kode
Async IO baru ini sangat bagus untuk contoh sederhana, tetapi pada IO kompleks tingkat server mungkin ada batasannya.
Saya sudah membuka isu terkait di GitHub
Masalah intinya adalah perancang bahasa atau library harus menyediakan cara untuk menghubungkan konteks eksekusi yang berbeda (sinkron/asinkron).
Untuk itu diperlukan pendekatan membungkus konteks dalam FSM (finite state machine), lalu menyediakan saluran komunikasi di antara keduanya
Tulisan terkait: Function colors represent different execution contexts