4 puan yazan GN⁺ 2025-12-04 | 1 yorum | WhatsApp'ta paylaş
  • Zig dili, mevcut asenkron I/O tasarımındaki karmaşıklığı azaltmak amacıyla yeni bir Io arayüzü tabanlı model benimsiyor
  • Bu model, senkron ve asenkron kod ayrımı yapmadan aynı fonksiyon yapısını koruyor ve Io.Threaded ile Io.Evented olmak üzere iki uygulama sunuyor
  • Io.Threaded varsayılan olarak senkron çalıştırma yaparken, Io.Evented olay döngüsü tabanlı asenkron çalıştırma gerçekleştirir
  • Geliştiriciler async() ve concurrent() 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, Io jenerik arayüzü etrafında çalışıyor
    • Tüm I/O işlevleri bir Io örneğini parametre olarak alır
    • Allocator arayüzüne benzer şekilde, bellek tahsisi gibi bir modelle I/O kontrol edilebilir

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şleme
    • Io.Evented: Olay döngüsü tabanlı asenkron çalıştırma (io_uring, kqueue gibi)
  • Kullanıcılar doğrudan yeni Io uygulamaları 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ştirir
    • Io.Threaded kullanıldığında normal sistem çağrılarıyla çalışır
    • Io.Evented kullanı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 ancak Io.Threaded içinde anında da çalıştırılabilir
    • Io.Evented iç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ır
    • Io.Threaded iş parçacığı havuzunu kullanır
    • Io.Evented async() ile aynı şekilde davranır
  • Yanlış fonksiyon seçimi (async yerine concurrent) 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, defer gibi 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.Evented henüz deneysel, bazı işletim sistemlerinde desteklenmiyor
  • WebAssembly uyumlu bir Io uygulaması planlanıyor ve ilgili işlevlerin geliştirilmesi gerekiyor
  • Io ile 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, Io arayü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 Io uygulaması 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

 
GN⁺ 2025-12-04
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ğinde async() aslında asenkron çalışmaz ve hemen yürütülür. Ancak std.Io.Threaded varsayılan olarak asenkron işleri dağıtmak için bir thread pool kullanır.
    Yalnız, init_single_threaded ile başlatılırsa makalede anlatıldığı gibi davranır.
    Bir de eskiden asyncConcurrent() diye bir fonksiyon vardı, ama artık adı sadece concurrent() oldu

    • Ben Daroc. Bu geri bildirimi yansıtarak makaleye iki düzeltme uyguladım.
      İ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
    • Andrew’a bir sorum var.
      async() kullanılması gereken yerde yanlışlıkla asyncConcurrent() 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östermesi
  • Bu 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

    • Eğer sadece farklı argümanlarla çağrıldığı için bir fonksiyon “renklenmiş fonksiyon” sayılacaksa, o zaman tüm fonksiyonlar renklenmiş olur ve kavram anlamını yitirir ;)
      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
    • Aslında function coloring’in temel sorunu senkron/asenkron kod yollarının yinelenmesi.
      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
    • Zig’in io sistemi bulaşıcı bir effect type değil.
      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
    • Esasen Zig her şeyi async ile renklendirip yalnızca worker thread kullanılıp kullanılmayacağını seçmenize izin veriyor gibi görünüyor.
      Ancak bu yaklaşım deadlock gibi sorunlara yol açabilir.
      Bazı kodlar thread-safe olmadığı için coloring bazen gerçekten faydalıdır
    • Bir Haskell geliştiricisi olarak bakınca, Zig sanki dil desteği olmadan bir IO monad uygulamış gibi görünüyor
  • 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ış

    • OS thread’lerini doğrudan kullanırsanız Little’s Law gereği ölçeklenebilirlik sınırına takılırsınız.
      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
    • Bu arada Scala’nın ExecutionContext API dokümantasyonuna bakarsanız ilgili kavramları daha iyi anlayabilirsiniz
  • 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ü

    • @asyncSuspend ve @asyncResume adı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
    • Nihayetinde suspend/resume özelliği user-space standard library fonksiyonu olarak uygulanabilir.
      Mevcut Io.Evented prototipine bakılırsa, bu iş stackless coroutine tabanlı olarak 3rd-party kütüphanelerde de ele alınabilir
    • Tek bir thread pool ile suspend/resume uygulanıp uygulanamayacağını da merak ediyorum
    • İşbirlikçi coroutine’leri preemptive async ile uygulamanın ne anlam ifade ettiği de ayrıca tartışmalı
  • Ö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 createFile ile writeAll arası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ç

    var a_future = io.async(saveFile, .{io, data, "saveA.txt"});
    var b_future = io.async(saveFile, .{io, data, "saveB.txt"});
    const a_result = a_future.await(io);
    const b_result = b_future.await(io);
    

    Rust veya Python’da coroutine’ler await edilmezse ilerlemez.
    Buna karşılık Zig örneğinde io.async kendi 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

    • C# da benzer çalışır. async fonksiyon, yield öncesine kadar çağıran thread üzerinde çalışır
    • Zig’de de aynı şekilde, yürütmenin garanti edilmesi için .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
    • Pratikte yürütme await noktası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
    • JavaScript de bu şekilde çalışır
  • 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 select anahtar sözcüğü var ama bunu socket’lerle kullanamamak bana hep eksik gelmiştir

    • Tüm IO’yu channel içine sarmanın maliyetli olduğu belirtiliyor.
      Go’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
    • Zig’de Go’daki channel’lara benzer bir std.Io.Queue bulunuyor.
      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
    • Odin dilini kullanıp kullanmadığınızı merak ediyorum. Go’dan Zig’e kıyasla daha fazla ilham alan bir “better C” dili
    • C#’ın async/await’inde olduğu gibi renklenmiş fonksiyonları zorunlu kılmamasını beğeniyorum.
      Zig’in “colorless” yaklaşımının çok daha iyi olduğunu düşünüyorum
    • Go’nun concurrency modelinin özel bir şey olduğu yanılgısı sorunlu.
      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