- Zig dili, mevcut asenkron I/O tasarımındaki karmaşıklığı azaltmak amacıyla yeni bir
Ioarayüzü tabanlı model benimsiyor - Bu model, senkron ve asenkron kod ayrımı yapmadan aynı fonksiyon yapısını koruyor ve
Io.ThreadedileIo.Eventedolmak üzere iki uygulama sunuyor Io.Threadedvarsayılan olarak senkron çalıştırma yaparken,Io.Eventedolay döngüsü tabanlı asenkron çalıştırma gerçekleştirir- Geliştiriciler
async()veconcurrent()işlevleriyle eşzamanlı yürütmeyi kontrol edebilir ve kodu değiştirmeden performans optimizasyonu yapabilir - Bu yaklaşım, fonksiyon renklendirme sorununu çözerken, Zig’in sadelik ve kontrol edilebilirliğini koruyarak asenkron performans sağlamayı hedefliyor
Zig'in Asenkron Tasarımındaki Değişim
- Zig, mevcut asenkron tasarımın dilin minimalist felsefesi ile iyi örtüşmediğini görerek yeni bir yaklaşım aradı
- Eski tasarımın diğer özelliklerle entegrasyon seviyesi düşüktü
- Yeni model, senkron/asenkron I/O'yu aynı kod yapısıyla işleyebilir
- Yeni tasarım,
Iojenerik arayüzü etrafında çalışıyor- Tüm I/O işlevleri bir
Ioörneğini parametre olarak alır Allocatorarayüzüne benzer şekilde, bellek tahsisi gibi bir modelle I/O kontrol edilebilir
- Tüm I/O işlevleri bir
Io Arayüzünün Yapısı
- Standart kütüphanede iki temel uygulama bulunuyor
Io.Threaded: Varsayılan olarak senkron çalıştırma, gerektiğinde iş parçacığıyla paralel işlemeIo.Evented: Olay döngüsü tabanlı asenkron çalıştırma (io_uring,kqueuegibi)
- Kullanıcılar doğrudan yeni
Iouygulamaları yazabilir, böylece yürütme biçimi üzerinde ayrıntılı kontrol sağlayabilir
Kod Örneği ve Çalışma Şekli
- Örnek
saveFile()işlevi dosya oluşturma, yazma ve kapatma adımlarını gerçekleştirirIo.Threadedkullanıldığında normal sistem çağrılarıyla çalışırIo.Eventedkullanıldığında asenkron bir arka uçla çalışır- İki durumda da
writeAll()çağrısı yapılınca işin tamamlandığı garanti edilir
- Aynı kod, senkron ve asenkron ortamların her ikisinde de aynı şekilde çalışır
- Kütüphane yazarı çalıştırma biçimiyle ilgilenmek zorunda kalmaz
Paralel Yürütme ve async() / concurrent()
async()işlevi asenkron çalıştırmayı talep eder ancakIo.Threadediçinde anında da çalıştırılabilirIo.Eventediçinde ise gerçek asenkron çalıştırma ile iki dosya aynı anda kaydedilebilir
concurrent()işlevi gerçek paralel çalıştırmanın gerektiği durumlarda kullanılırIo.Threadediş parçacığı havuzunu kullanırIo.Eventedasync()ile aynı şekilde davranır
- Yanlış fonksiyon seçimi (
asyncyerineconcurrent) hata olarak kabul edilir ve dil düzeyinde engellenmesi mümkün değildir
Kod Stili ve Dil Bütünleşmesi
- Asenkrona özel bir sözdizimi olmadan normal Zig kod stili korunur
try,defergibi mevcut kontrol akışı sözdizimleri aynen kullanılır- Andrew Kelley, bunun “standart bir Zig kodu gibi okunduğunu” belirtiyor
- Örnek olarak asenkron DNS sorgusu gösterildi
getaddrinfo()yerine ilk başarılı yanıt döndürülür ve kalan istekler iptal edilir
Gelecek Planlar ve Geliştirme Durumu
Io.Eventedhenüz deneysel, bazı işletim sistemlerinde desteklenmiyor- WebAssembly uyumlu bir
Iouygulaması planlanıyor ve ilgili işlevlerin geliştirilmesi gerekiyor Ioile ilgili 24 takip görevi var ve çoğu hâlâ tamamlanmamış durumda- Zig henüz 1.0'a gelmedi; asenkron I/O ve yerel kod üretimi hâlâ ana kalan meselelerdir
- Bu modelle I/O arayüzü değişiklikleri nedeniyle kod yeniden yazma sıklığının azalması bekleniyor
Topluluk Tartışmasının Özeti
- Yorumlarda birçok kişi, Zig’in yaklaşımının Rust’ın async/await modelinden daha basit ve esnek olduğunu değerlendirdi
- Rust’ta farklı executor’ların birlikte kullanımıyla karmaşıklık artabiliyor
- Zig,
Ioarayüzüyle çoklu executor’ların bir arada var olmasını mümkün kılıyor
- Bazıları kodun biraz uzun olabileceğini eleştirdi
- Ancak açık API tasarımıyla güvenlik, performans ve test kontrolünün iyileşmesi hedefleniyor
- Asenkron çalıştırma ile iş parçacığı çalıştırmasının farkı, stackful vs stackless coroutine gibi teknik konular da tartışılıyor
- Zig’in
Iouygulaması dil düzeyinde özel bir işleme ihtiyaç duymadan standart kütüphane genişlemesi biçiminde uygulanıyor- İleride stackless coroutine özelliğinin eklenmesi planlanıyor
Sonuç
- Zig’in yeni asenkron modeli, dil sadeliğini korurken yüksek performanslı I/O hedefler
- Fonksiyon renklendirme sorunu çözümü, senkron/asenkron kod entegrasyonu ve açık kontrol yapılarıyla Zig 1.0 kararlılığı için kritik bir adım olarak görüldü
1 yorum
Hacker News görüşü
Genel olarak bu yazı doğru ve iyi araştırılmış.
Ancak birkaç küçük düzeltme var.
Io.Threadedörneğindeasync()aslında asenkron çalışmaz ve hemen yürütülür. Ancakstd.Io.Threadedvarsayılan olarak asenkron işleri dağıtmak için bir thread pool kullanır.Yalnız,
init_single_threadedile başlatılırsa makalede anlatıldığı gibi davranır.Bir de eskiden
asyncConcurrent()diye bir fonksiyon vardı, ama artık adı sadececoncurrent()olduİleride geri bildirim vermek isterseniz lwn@lwn.net adresine e-posta gönderebilirsiniz.
Düzeltme önerileri ve Zig ile ilgili çalışmalar için teşekkürler
async()kullanılması gereken yerde yanlışlıklaasyncConcurrent()kullanılırsa nasıl bir bug ortaya çıkar, merak ediyorum.IO modeline bağlı olarak bunun UB (tanımsız davranış) olup olamayacağını, yoksa sadece mantıksal bir hata mı olduğunu öğrenmek istiyorum
concurrent()fonksiyonunun güzel yanı, kodun okunabilirliğini ve ifade gücünü artırıp “bu kod mutlaka paralel çalışmalı” durumunu açıkça göstermesiBu tasarımın oldukça makul olduğunu düşünüyorum.
Yalnız Zig’in açıklaması kafa karıştırıcı.
Function coloring sorununu çözdüğünü vurguluyor, ama gerçekte yaptığı şey IO’yu bir effect type içine itmekten ibaret.
Bu da çağıranın bir token’ı elinde tutmasını gerektiren bir yapı; yani hâlâ bir tür coloring söz konusu.
Bence Go’nun asenkron işleme yaklaşımına benziyor
Zig’in eski async-await modeli de coloring sorununu zaten çözmüştü.
Çünkü derleyici çağrı bağlamına göre senkron/asenkron sürümleri otomatik oluşturuyordu
Zig bunu dependency injection ile çözüyor ve pratikte bu yeterince iyi.
async çağrıların karmaşıklığından kaçınmak mümkün değil, ama hassas kontrol için bu kaçınılmaz bir bedel
Her yerden kullanılabilecek global bir io değişkeni tanımlayabilirsiniz (tabii kütüphane yazarken bu önerilmez).
Function coloring sorununun beş koşulunu özetleyen What color is your function? yazısına bakarsanız, Zig yaklaşımının bazı koşulları (özellikle 4 ve 5) karşılamıyor olma ihtimalinin yüksek olduğunu görürsünüz
Ancak bu yaklaşım deadlock gibi sorunlara yol açabilir.
Bazı kodlar thread-safe olmadığı için coloring bazen gerçekten faydalıdır
Bu tasarım Scala’nın async yapısına oldukça benziyor.
Scala’da execution context bir örtük parametre olarak aktarılırken, Zig bunu açıkça alıyor.
Pratikte, thread ve queue’ları doğrudan kullanmaya kıyasla çok da üstün değildi ve execution context yönetimi karmaşık ve öngörülemez davranışlara yol açıyordu.
Görünüşe göre Zig ekibi Scala deneyimine pek sahip olmadığı için bu yaklaşımı yeni sanmış
JVM bunu virtual thread ile çözüyor, ancak düşük seviye dillerin aynı verimliliği yakalaması zor.
Bu yüzden Zig gibi dillerin ölçeklenebilirlik için farklı çözümlere ihtiyacı var
Zig’in eski async/await sisteminde fonksiyonları suspend/resume etmek mümkündü.
Bu özellikle OS geliştirirken aygıt kesmeleri tabanlı frame askıya alma/devam ettirme mekanizması kurmak için denemek istediğim bir şeydi.
Yeni io sisteminde bunu doğrudan kendim yazmam gerekecek gibi görünmesi biraz üzücü
@asyncSuspendve@asyncResumeadında düşük seviye built-in’ler var.Yeni Io; senkron, thread ve event tabanlı yapılar için ortak bir soyutlama olduğu için suspend mekanizmasını içermiyor
Mevcut Io.Evented prototipine bakılırsa, bu iş stackless coroutine tabanlı olarak 3rd-party kütüphanelerde de ele alınabilir
Örnek kodda
writeAll()döndüğünde işin tamamlandığı söylenmiş,ancak IO uygulamaları farklı olabileceği için gerçekte tamamlanmanın defer başladığında garanti edilmesi gerekir.
Aksi hâlde
createFileilewriteAllarasındaki bağımlılık ilişkisini izlemek gerekir.O durumda da sonuçta blocking çağrıdan çok farklı görünmüyor.
Ayrıca bu arayüzün neden IO adını taşıdığı da belirsiz.
Aslında daha çok “başka bir bağlamda çalıştırma” soyutlamasına benziyor
İlgili doküman: std.Io
Şu örnek ilginç
Rust veya Python’da coroutine’ler await edilmezse ilerlemez.
Buna karşılık Zig örneğinde
io.asynckendi kendine ilerliyorsa, bu daha çok task oluşturma modeline benziyor.Bu geçerli bir tasarım tercihi, ama diğer dillerin izlediği yön bu değil
asyncfonksiyon, yield öncesine kadar çağıran thread üzerinde çalışır.await(io)çağrılmalıdır.Hemen çalıştırılıp çalıştırılmayacağı ya da thread pool’a kuyruğa alınıp alınmayacağı Io runtime implementasyonuna bağlıdır
awaitnoktasında ilerler.Evented io durumunda iki iş interleaved çalışabilir, threaded io’da ise arka planda ilerleyebilir.
Yani “bir yerlerde gizlice çalışan task” diye bir şey yok
Go’yu her gün kullanan biri olarak, Zig’in Io yaklaşımı bana Go’nun çeşitli eksik yönlerini düzeltiyormuş gibi geliyor.
Yine de Zig’de channel kavramı olup olmadığını merak ediyorum.
Go’da
selectanahtar sözcüğü var ama bunu socket’lerle kullanamamak bana hep eksik gelmiştirGo’nun channel’larında onlarca cycle seviyesinde overhead var; bu yüzden küçük ölçekli IO için verimsiz kalıyor.
Buna karşılık büyük veri aktarımı veya çoktan çoğa senkronizasyon için faydalılar
std.Io.Queuebulunuyor.select benzeri yapılar da kurulabiliyor, ancak sözdizimi açısından daha az ergonomic.
Buna karşılık GC olmadan farklı IO runtime’ları üzerinde çalışabilme avantajı var
Zig’in “colorless” yaklaşımının çok daha iyi olduğunu düşünüyorum
Goroutine’ler sadece green thread, channel’lar ise thread-safe queue; Zig de bunları zaten standart kütüphanede sunuyor
Zig’in async sürüm Io’su, Go’nun yaklaşımıyla neredeyse aynı görünüyor.
Ama Go’da C kütüphanelerini çağırırken stack allocation maliyeti yüksek oluyor ve doğrudan syscall kullanımı da platform uyumluluğu sorunları doğuruyor.
Görünüşe göre Zig bunu yapılandırılabilir hâle getirerek kodu değiştirmeden farklı trade-off’lar seçebilmenizi sağlıyor
Yeni async IO basit örneklerde harika, ancak sunucu düzeyinde karmaşık IO için sınırları olabilir.
Bununla ilgili bir issue’yu GitHub üzerinde açtım
Asıl mesele, dil veya kütüphane tasarımcılarının farklı yürütme bağlamlarını (sync/async) birbirine bağlayacak bir araç sunması gerektiği.
Bunun için bağlamı bir FSM (sonlu durum makinesi) içinde sarmalayıp iki taraf arasında bir iletişim kanalı sağlamak gerekiyor
İlgili yazı: Function colors represent different execution contexts