Rust muhtemelen büyük ölçekli eşzamanlı kullanıcı alanı yazılımları için uygun olmayabilir
(bitbashing.io)- Rust’ın
async/awaityapısı, on binlerce bağlantıyı ele alan büyük ölçekli eşzamanlılığı hedefliyor; ancak düşük seviye kontrol ve statik yaşam süresi doğrulaması gibi Rust hedefleriyle çatışarak normal Rust’tan farklı bir geliştirme deneyimi yaratıyor - Thread ve channel birçok yazılım için yeterli olsa da, C10K ölçeğinde bağlantı başına thread yaklaşımının yükü büyüyor ve bu da kullanıcı alanı task’leri ile çalışma zamanı zamanlamasını gerekli kılıyor
asyncRust’ta verilerinSendile taşınması ya da'staticreferanslar olarak ele alınması gerekiyor; ayrıcaasync’in bulaşıcı yapısı yüzünden bu kısıtlar kod tabanının geneline tekrar tekrar yayılıyorArc, derleme sorunlarını çözse de nesnelerin ve kaynakların yaşam süresini belirsizleştiriyor; özyinelemeliasync, future ile task arasındaki fark ve blocking çağrıların çalışma zamanı thread’lerini durdurması gibi tuzaklar da devam ediyor- Haskell veya Go’da “async code” normal kod gibi çalışıyor; çalışma zamanı ve GC farkları gizlediği için, bu tür programlamada Rust’ın açık kontrol yaklaşımı her zaman net bir avantaj olarak işlemeyebilir
Eşzamanlılık ve paralelliğe neden ihtiyaç var
- Hızlı programların aynı anda karşılaması gereken iki gereksinim vardır
- Birden fazla CPU çekirdeğini kullanarak bilgisayarın tamamından yararlanmak gerekir
- İnternet üzerinden mesaj gönderme veya dosya açma gibi yavaş işlemler beklenirken başka işler yapılmaya devam edilmelidir
- Paralellik, kodun birden fazla CPU’da aynı anda çalıştırılması problemidir
- Eşzamanlılık, problemi bağımsız parçalara ayırma biçimidir
- Bunlar aynı şey değildir; ancak programı eşzamanlı parçalara bölmek, bu parçaların paralel çalışmasını ve çekirdeklerin sürekli meşgul kalmasını sağlayabilir
Process, thread ve channel
- Eşzamanlı sistem kurmanın basit bir yolu, kodu birden fazla process’e bölmektir
- İşletim sistemi zamanlayıcısı, çalışmaya hazır process’lerin zaman dilimlerini kullanılabilir CPU çekirdeklerinde yürütür
- Shell komutlarını pipe ile bağlama durumunda da bu model kullanılır
- Process yaklaşımında process’ler arası iletişimin maliyeti yüksektir
- Birçok uygulamada verinin OS belleğine kopyalanıp sonra geri alınması gerekir
- Paylaşımlı bellekle maliyet azaltılabilir, ancak OS’nin process’leri birbirinden yalıtma avantajı da zayıflar
- Thread’ler aynı belleği paylaşarak bu ek yükten kaçınır; ancak mutex, condition variable, semaphore gibi eşzamanlama araçları yanlış kullanılırsa veri yarışı ve deadlock ortaya çıkabilir
- Tony Hoare’un Communicating Sequential Processes modeli, thread’leri kuyruklar veya channel’lar ile bağlar
- Thread’ler belleği paylaşmadığı için process benzeri bir yalıtım elde edilir
- Her thread’in girdi ve çıktısı channel’lar üzerinden görünür olduğundan akıl yürütmek ve hata ayıklamak kolaylaşır
- Channel’ların kendisi eşzamanlama görevi görür; boşsa alıcı bekler, doluysa gönderici bekler
- Rust standart kütüphanesinde std::sync::mpsc::sync_channel bulunur
- Birçok yazılım için thread ve channel ile CPU yoğun döngüleri paralelleştiren Rayon gibi araçların birleşimi yeterlidir
Kullanıcı alanı eşzamanlılığı ve Rust async
- On binlerce eşzamanlı kullanıcının bağlandığı web sunucuları gibi C10K problemlerinde, bağlantı başına bir thread atama yaklaşımı sınırlarına ulaşır
- Linux’ta her thread 4kB’lik bir control block taşır ve thread geçişleri, işletim sistemi zamanlayıcısına giren bir context switch gerektirir
- Büyük ölçekli eşzamanlılık için bazı diller task’leri kullanıcı alanında oluşturur ve yönetir
- Çalışma zamanı bu task’leri bir OS thread havuzu üzerine zamanlar
- Genellikle havuz, CPU çekirdeği başına bir thread düşecek şekilde yapılandırılarak paralellik en üst düzeye çıkarılır
- Bu yaklaşım green thread, lightweight thread, lightweight process, fiber, coroutine gibi adlarla anılır
- Rust, C# veya Node.js’te görülen
async/awaitmodelini kullanırasync fn, doğrudan değer döndürmek yerine sonucun.awaitile alındığı bir future veya promise döndürür
- Rust future’ları, işbirlikçi zamanlama ve stackless tasarım sayesinde çok küçük ve hızlıdır
- Rust, future soyutlamasını sunarken aynı zamanda programcıya düşük seviye kontrol vaat etmeye çalışır
- Tüm nesnelerin ve referansların yaşam süresini derleme zamanında statik olarak doğrulamayı hedefler
- Future’lar kodu ve kodun referans verdiği verileri binlerce parçaya böler; bunlar çalışmaya başladıktan sonra ancak anlaşılabilecek koşullara göre, herhangi bir anda herhangi bir thread’de yürütülebilir
- İstemci verisini okuyan bir future yalnızca ilgili sokette okunacak veri olduğunda çalıştırılmalıdır; fakat lifetime annotation bunun ne zaman olacağını söylemez
- Rust, future çalışma zamanını dile gömmez; bunu Tokio gibi kütüphanelere bırakır
- Böylece kullanıcılar ortamlarına uygun alternatifleri seçme özgürlüğü kazanır
- Ancak Tokio’nun dile gömülü olduğu bir dünya hayal edilse bile aynı kurallar geçerli olacağından, bu tartışmada ikincil bir ayrıntıdır
Send, 'static ve Arc’ın yarattığı baskı
- Derleyiciyi ikna edebilmek için verilerin ya
Sendolarak işaretlenip taşınması ya da'staticyaşam süresine sahip referanslar üzerinden aktarılması gerekir asynckodda birden fazla task’in ortak durumu paylaşması yaygındır; bu yüzden veriyi kopyalamadan taşıma yaklaşımı sık sık uygun düşmez- Referanslar da zordur; ayrıca future’ın yaşam süresini “sonsuz”dan daha kısa sınırlayan
thread::scopebenzeri bir karşılık yoktur asyncbulaşıcıdır;asyncfonksiyon çağıran fonksiyonun daasyncolması gerekir- Bu nedenle yaşam süresi ve taşınabilirlik sorunları birkaç fonksiyonda değil, kod boyunca sürekli çözülmek zorundadır
- Zincir, çalışma zamanında future tamamlanmasını
block_onile bekleyerek kırılabilir; ancak bu yaklaşım birleştirilebilir değildir ve iç içe kullanıldığında çalışma zamanı panic üretebilir
- Arc, dinamik yaşam süresini birden çok thread arasında ele alma aracıdır; borrow check’i geçmeye ve kodun derlenmesine yardımcı olur
- Ancak
Arc’ın yaygın kullanımı nesnelerin ve kaynakların yaşam süresini bulanıklaştırır- Bellek, dosya, soket gibi kaynakların ne zaman serbest bırakılacağı net olmaz
- Gerçek GC’nin sunduğu allocation throughput, düşük fragmentation ve cycle leak önleme gibi avantajlar olmadan, GC’ye benzer kayıplar yaşanır
async Rust’ın ek tuzakları
- Rust coroutine’leri stackless olduğu için derleyici her coroutine’i
.awaitnoktasına kadar ilerleyen bir durum makinesine dönüştürür- Özyinelemeli
asyncfonksiyonlar özyinelemeli tanımlanmış tiplere dönüşür - Sadece kendini çağırmak isteyen kullanıcı, bunu elle boxing yaparak ya da async-recursion gibi bir crate kullanarak çözmek zorundadır
- Özyinelemeli
- Future, await edilene kadar hiçbir şey yapmaz
- Task, çalışma zamanının thread havuzunda işi başlatır ve tamamlandığını bildiren bir future döndürür
- Future içinde blocking kod çağrılmasını engelleyen bir mekanizma yoktur
- Böyle bir çağrının üzerinde çalıştığı runtime thread’ini bloke etmesi de engellenmez
- Bu durum,
asynckullanmanın temel amacıyla çelişir
Normal Rust, Haskell ve Go arasındaki fark
asyncRust, “normal” Rust’tan belirgin biçimde farklı bir his verir- Daha fazla tuzak içerir
- Anlaması ve öğretmesi daha zordur
- Kullanıcılar iki seçenek arasında kalır
- Soyutlamanın gerçekte nasıl çalıştığını derinlemesine anlayıp karmaşık kod yazmak
Arc,Pin,'staticgibi öğeleri kodun her yanına serpiştirip işe yaramasını ummak
- Deneyimli geliştirici ekipleri bile yeni bir projede Rust kullanmaya çalışırken bu ayrıntılara takılabilir
- Haskell veya Go’da “async code”, normal koddur
- Bu iki dil, blocking ve non-blocking kod arasındaki farkı kalın bir çalışma zamanı katmanının arkasında gizler
- Yaşam süresi sorunları garbage collection’a bırakılır
- Bu tür büyük ölçekli eşzamanlı kullanıcı alanı yazılımlarında, çalışma zamanı ve GC’nin farkları gizlemesi başlı başına bir avantaj olarak işler
- Rust, büyük ölçekli eşzamanlı kullanıcı alanı yazılımları için iyi bir araç olmayabilir; böyle gereksinimleri olmayan projelerde kullanılması daha uygun olabilir
1 yorum
Hacker News yorumları
Yüksek performanslı bir metaverse istemcisini Rust ile yazıyorum; şu anda yaklaşık 40 bin satır civarında.
Demo videosu https://video.hardlimit.com/w/tp9mLAQoHaFR32YAVKVDrz adresinde.
Düzgün bir metaverse, kullanıcıların ürettiği içeriği neredeyse gerçek zamanlı işlemek zorunda olduğundan, benzer oyunlara kıyasla 2–3 kat VRAM, sunucudan asset yüklemek için yüzlerce Mbps bant genişliği, birden fazla CPU ve render ile GPU yüklemelerini paralel yürütecek Vulkan gerekiyor.
Bu, “web ölçeği” eşzamanlılıkta olduğu gibi aynı adres alanında küçük sunucuların ayrı ayrı çalıştığı bir yapı değil; yüksek öncelikli render thread’i, ağ olaylarını güncelleyen thread, asset yükleme ve sıkıştırma açma thread’leri, hareketli nesneler, LOD ve önbellek temizliği gibi işleri üstlenen çok sayıda thread’in birlikte hareket ettiği bir yapı.
Rust’ta sabitler dışında küresel durum kullanmadan epeyce kilit kullanıyorum; kanalları uygun yerlerde kullanıyorum ve ana nesne ağacını tekil sahiplikle çoğunlukla güncelleme thread’i yönetiyor. Grafik nesnesi bağlantıları
Arcreferans sayacıyla yönetiliyor; mesh ve texture’lar Rend3/WGPU/Vulkan üzerinden GPU’ya yükleniyor.Bunu C++ ile yapsaydım sürekli crash’lerle boğuşurdum; Rust’ta bellekle ilgili crash yılda yaklaşık bir kez oluyor ve o da genelde başkalarının
unsafekodundan kaynaklanıyor. Kendi kodumdaunsafekullanmayı yasakladım; derlemek zor, ama bir kez derlenince “öylece çalışıyor” denebilir, bu yüzden eşzamanlılık debug’ından çok daha iyi olduğunu düşünüyorum.Şikâyetlerim de var. Rust veri yarışlarına karşı güçlü, ama deadlock’ları engelleyemiyor; bu yüzden çağrı yolları boyunca kilit sırasını izleyen bir statik analiz aracı gerekiyor.
async, hesaplama ağırlıklı işler ve farklı önceliklere sahip thread’ler için uygun değil, ama sürekli bağımlılıklara sızıyor. Tekil sahipliğe ters referansların eklendiği yaygın yapılarRcveWeakolmadan aşırı zor; trait sistemi de karmaşık olduğundan, nesne yöneliminin doğal olduğu asset işleme kısımlarında yinelenen kod ortaya çıkıyor.Grafik tarafındaki temel crate’ler de henüz yeterince olgun değil. “Rust’ta 5 oyun ve 50 oyun motoru var” sözü dilin değil ekosistemin sorunu; https://gamedev.rs/ ile karşılaştırınca bile Rust ile ciddi oyun geliştirmenin hâlâ yetersiz olduğu görülüyor. Takvimi olan profesyonel oyun geliştirme için Rust oyun ekosistemi henüz hazır değil; kabaca 5 kişinin bir yıl kadar daha çalışması gereken seviyede olduğunu düşünüyorum.
asyncile ilgili sorunlar olsa da genel olarak avantajları çok daha büyük.Linux’taki
lockdepgibi, bir kilit tutulurken başka hangi kilitlerin alındığını analiz edip, gerçekten durmadan önce bile riskli kombinasyonları bildirebilir. Karmaşık kilitlerde “bu kilit sınıfı her zaman adres sırasına göre alınır” gibi açıklamalar gerekebilir, ama uygulanabilir görünüyor.Veri erişiminin dışında hâlâ yarış koşulları oluşabilir: https://news.ycombinator.com/item?id=23599598
Birden fazla thread pool oluşturup future’ları uygun şekilde yönlendirebilir ya da kendi event loop’unuzu yazarak farklı önceliklere sahip birden çok event queue’dan çekebilirsiniz. İkinci yöntem, işlerin çalışma süresi sınırlıysa CPU %100 doluyken bile düşük öncelikli işleri ilerletirken yüksek öncelikli işlere soft real-time garantisi verebilir.
asyncRust konusunda tuhaf bir noktadayız.Arc,RwLockve bolca paylaşılan durum kullanınca işlerin kirlendiği; özellikle'staticher yere yayılmaya başladığında renkli fonksiyonlar gibi her şeyi enfekte ettiği eleştirisi doğru. EskidenArcekleyip lifetime borrowing’i akıllıca yönetmeye çalışırken ortalığı batırmıştım.Ama Rust’ta kanallar da var. Şu anda yazdığım kodların çoğu, birkaç görevin kanalları servis ettiği; gelen mesaja bakıp gerekirse başka bir göreve gönderilecek mesajı uygun kanala koyduğu bir yapıda. Nesne paylaşmıyorum. Birden fazla görev büyük bir nesneye ihtiyaç duyarsa, ilgili sorgu sonuçlarını mesaj olarak gönderen bir görevin içinde tutuyorum ya da her görevin mesaj akışında kendi kopyasını oluşturmasını sağlıyorum.
Buna rağmen
Arc’ın nasıl kullanılacağı ve lifetime’ların nasıl ele alınacağı üzerine çok fazla yazı var. Birasyncruntime’ı uyguluyorsanız gerekli olabilir, ama ortalama bir kütüphane kullanıcısının neden buna bu kadar odaklanması gerektiğini pek anlayamıyorum.asyncdoğrudan çok thread’li olmak demek değil; aynı thread içindekiasyncise paylaşım olmadığı için paylaşılan şeylerin tamamına sihirli anahtar kelimeler eklemek de gerekmez.Thread’ler arasında geçiş yaparken bolca paylaşılan durum tutmak yerine kanallarla sinyal gönderilir. Mutlaka gerekli bir küresel durum varsa
Arc/RwLockgibi özel erişim mekanizmalarını saran küçük bir struct oluşturup, çağıran açısından basit bir fonksiyon çağrısı gibi görünmesini sağlarsınız.Send+Synckonusundaki endişeyi de pek anlamıyorum. Deneyimime göre çoğu şey kolaycaSend+Syncoluyor; olmayanlar da öyle olmaması gereken ya da olamayan şeylerdi. Ayrıntıları düşünmeden kod yazmak istediğim zamanlar da oluyor, ama verimli eşzamanlılık ve paralellik gerekiyorsa mikrosaniyeler ve throughput önemli hâle gelir; o noktada gerçek bilgisayar kodunu düzgün yazmanız gerekir.Ama bu tarz kodlama, senkron koda green thread eklenmiş gibi hissettiren
asyncJavaScript’ten epey farklı. İnsanlar alıştıkları şekilde kod yazmaya çalıştıkları için Rust’taArcveRwLockyoluna sapıyor gibi görünüyor.Problemi görevler arasında akan veri olarak yapılandırmak, kuyruklarla bağlamak ve paylaşılan durumdan kaçınmak, hangi dili kullanırsanız kullanın çok thread’li programlamayla başa çıkmanın daha iyi bir yolu.
asyncaslında çok daha zor bir Rust; gerçekten ihtiyaç duyan projeler muhtemelen %1 civarındadır, ama neredeyse herkese dayatılmış gibi olması üzücü.Yine de o %1 içinde gerçekten harika. linkerd veya nginx gibi çok sayıda ağ çağrısını merkezinde işleyen servislerde, oyunlarda muazzam sayıda hafif iş çalıştırılan durumlarda, gömülü sistemlerde işbirlikçi eşzamanlılık gerektiğinde async Rust güçlü bir silaha dönüşüyor.
Sistem ve uygulama düzeyindeki kodların çoğunda asenkron giriş/çıkışa gerek yok. REST uygulamaları için thread pool yeterli;
asyncgerekse bile bunu ağ gibi küçük bir kısımla sınırlayıp geri kalanını thread’ler ve channel’larla bağlayan karma model genelde doğru olanı.Rust topluluğu
asynci her yerde fazla düşünmeden kullandığı için, kullanıcı deneyimi daha iyi olan blocking I/O Rust ekosistemde ikinci sınıf vatandaş hâline geldi. Web framework’lerinde de Axum, Warp gibi iyi tasarlanmış birkaç asenkron framework var; blocking tarafta isetiny_http,rouille,astragibi seçenekler çok daha sınırlı.Stack’siz coroutine’ler seçilince
async/awaitve renkli fonksiyon problemi ortaya çıktı; yazıda bahsedilen sürtünme de bundan kaynaklanıyor. Go stack’li coroutine kullandığı için bu sorun yok.Rust da başlangıçta stack’li coroutine’leri değerlendirdi, ancak coroutine preemption runtime’ı gerektirdiği ve maliyetli olduğu düşünülerek stack’siz modele gidildi. Ama çoğu kişi runtime’sız
asyncRust kullanmıyor, Tokio kullanıyor; Tokio da kaçınılmak istenen runtime’ın yaptığı işleri fiilen neredeyse tamamen yapıyor.Böylece
asyncRust kullanıcılarının çoğu iki dünyanın da kötü yanlarını bir arada almış oluyor. Gömülü tarafta çok ince bir runtime ileasyncRust kullananlar da var; ama sayıları az ve onların bile tamamen ikna olduğu söylenemez.reqwesti getiriyor, oh2yi getiriyor, o da tekrartokioyu getiriyor.asynckullanmak için bir sebep olup olmadığını merak ediyorum.Java kullanan biri olarak, tüm asenkron paradigmayı bırakıp blocking’in sorun olmadığı virtual thread’ler üzerindeki blocking modele göre kodu yeniden yazmaya çalışıyorum.
asynco kadar çok crate’e yayılmış durumda ki programın tamamıasyncoluyor ya da en azından pek çok iş için Tokio’ya bağımlı hâle geliyor.Web sunucusu istiyorsan “
async + tokiokullan ya da defol” deniyormuş gibi; SQL connector’larında da asenkron istemiyorsan kendin yazman gerekiyormuş havası var. Herkesasyncin getirdiği sorunları farklı bir şekilde çözüyor;asyncclosure gibi şeyler ise derleyiciye cehennemin kapılarını açıyor hissi veriyor.Rust’ın kendisinin ve derleyicinin sorun çözmeye yardımcı olması güzel, ama ekosistemin “
asyncdeğilse kendin yap” noktasına yakın olması yeterli değil.futurescrate’inde daha iyi asenkron temel bileşenler olsaydı acı çok azalabilirdi.Executor’ın uygulaması gereken trait’ler veya senkron koddan asenkron kod çalıştıracak temel bir blocking executor gibi şeyler gerekiyor. Şu anda birden fazla asenkron runtime’ı destekleyen kütüphane yazmak bile eziyet; sonuçta yalnızca Tokio destekleniyor ya da en fazla
async-stdekleniyor.asyncRust uzmanı değilim, ama bu ay birkaç bin satır senkron Rust yazarken şunu hissettim:rustcbir yaklaşımı zorlaştırıyorsa bunun genelde iyi bir nedeni oluyor ve benzer sonucu daha iyi bir yolla elde etmenin bir yolu bulunuyor.Dili öğrenme aşamasındaysanız önce sıradan senkron kod, döngüler ve koşullar ile borrow kurallarına alışmanızı öneririm.
async, yalnızca uygulama tarafında değil, “asenkron nedir ve kullanıcıya nasıl görünmelidir” gibi felsefi düzeyde de hâlâ epey gelişim içinde.Derleyici trait’lere büyük ölçüde dayanıyor, ancak trait’lerin
asynci ele alma özellikleri stabilize edilmiş değil. Örneğin https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-i... gibi çalışmalar var.Trait’lerdeki asenkron özellikler stabilize edilmemişse, Rust’ın asenkron kodu henüz güzel değil diye saldırmak, sonunda tamamlanacak bir kitabın ilk taslağını eleştirmeye benziyor.
Asenkronluğun tüm codebase’e yayılması da nasıl engellenebilir, bunu düşünüyorum.
Şu anki fikrim, giriş/çıkış thread’inin
liburingveyaepollsistem olaylarını “submit” ve “handle” diye iki aşamaya ayırıp başka bileşenlere gönderen bir yapı. Örneğin birtcp-connectionoluşturduğunuzda “yazmaya hazır”, “okumaya hazır” gibi asenkron olaylara abone olabiliyorsunuz; yazmaya hazır olayı, normal bir mutex ile doldurulmuş buffer’dan veriyi çıkarıpEPOLLOUT/io_uring_prep_writevile gönderiyor.Thread’ler arası olay iletimi için LMAX Disruptor deseninin çok üreticili·çok tüketicili ring buffer’ı kullanılabilir. Uygulama thread’i veya thread pool, her biri kendi event loop’una sahip olup bu ring buffer’ı işler.
Asenkron olayların tetiklenme sırasını ifade eden bir sözdizimi üzerinde de çalışıyorum; Bash pipeline’ına benziyor ve
statelinesadını veriyorum:initialstate1 initialstate2 = state1 | {state1a state1b state1c} {state2a state2b state2d} | state3Arc’nin yaşam süresi bilinmez değildir; nerede ve nasıl tutulduğuna göre belirlenirBu yazıdaki kopukluk, yazarın Rust’ı öğrenip dilin çalışma biçimine uyum sağlamasından ziyade, çöp toplama gibi önceki düşünce modellerini Rust’a zorla uygulamaya çalışmasından kaynaklanıyor gibi görünüyor. Yeni bir dil öğrenirken sık görülen bir tuzak, ama Rust insanı özellikle daha sık tökezletiyor
Ama bu, nesne yaşam sürelerini derleme zamanında statik olarak sınırlamaya çalışan ödünç alma denetleyicisinin hedefinin neredeyse tersidir
Gerçekte durum neredeyse tersiydi. C, C++, Rust ile yaklaşık 10 yıl sistem programlama yaptıktan sonra mevcut işimde Haskell’i yoğun kullanmaya başladım; büyük bir dil runtime’ının ve çöp toplamanın bazı problem alanlarında bir canavar olmadığını görmek epey ufuk açıcı oldu
asyncdönüşümünün, asenkron olmayan kodda derleyicinin yapabileceği optimizasyonları nasıl engellediğine dair bir yazı olacağını sanmıştımWeakile boğuştuğu kısım, karmaşık bir sahiplik yapısı kurmaya çalışıyormuş gibi görünüyor; bu da Rust genelinde kolay bir iş değil. Ben zayıf akıllı işaretçileri çok nadiren kullanırımKanallardan neredeyse hiç söz edilmiyor; oysa asenkron kodda veya asenkron ve senkron kod arasında bağlantı kurarken programın farklı bölümlerini haberleştirmenin başlıca aracıdırlar.
Notify, semafor gibi sinyal soyutlamaları da varMutex yavaştır ve kolayca darboğaz olur; paylaşılan durum da hızla karmaşıklaşır. Bu uzun zamandır bilinen bir gerçek. Sorun en başta
BIG_GLOBAL_STATIC_REF_OR_SIMILAR_HORRORbenzeri bir yapıda olabilirAsenkron bağlamda bloklayan kod çağırmayı engelleyemediği eleştirisi yerinde, ama gerekirse
tokio::spawn_blockinggibi şeylerle nispeten yönetilebilirYazarın
Arc’ın ne olduğunu ve nasıl çalıştığını biliyor olma ihtimali yüksek; asıl nokta, Rustasynciçinde senkron koda kıyasla genel RAII yerineArc’ı çok daha sık kullanmak zorunda kalmanız gibi görünüyorProgram nesnelerinin %90’ı referans sayımlıysa, çok sayıda küçük heap ayırma/serbest bırakma ve atomik işlem maliyetine katlanmaktansa izlemeli çöp toplama kullanmak daha iyi olabilir. Tokio eğitimindeki örnek de benzer bir yöne işaret ediyor: https://tokio.rs/tokio/tutorial/shared-state
Rust’ta gerçek bir izlemeli çöp toplamanın HTTP sunucusu gibi yaygın asenkron uygulamaları anlamlı biçimde hızlandırıp hızlandıramayacağını merak ediyorum: https://manishearth.github.io/blog/2015/09/01/designing-a-gc...
Arc’ın yaşam süresi rastgele değil, statik olarak bilinemez olandırArc’ı taşınabilir veya ödünç alınabilir; referans sayacına dokunmadan da kullanılabilirBirçok durumda, örtük referans sayımlı dillerdeki nesnelerden çok daha ucuzdur
Rust’ı seviyorum ama
asynckarmakarışık; senkron kod yazar gibi asenkron kod yazamazsınızİkisini karıştırmanın kötü bir fikir olduğuna giderek daha çok ikna oluyorum; her şeyi senkron bırakıp yalnızca tek bir
asynckanal ilkel öğesi sunan Go tarzı yaklaşım doğru olabilirŞu anda
Futureuygulayan bir struct içinden senkron metot çağıracak şekilde mantığı tesisatlandırıyorum; oldukça ilginç bir meydan okuma. Sıfır maliyetli asenkron soyutlamaları kullanıcı için bir ölçüde kolaylaştırabilirsiniz, ama acıyı kütüphane geliştiricisi üstlenirasyncson kullanıcı için de kesinlikle sancılı; Rust’ın temel özellikleri olan yaşam süreleri ve açık tiplerden yoksun ayrı bir dil kullanıyormuşsunuz ve üzerine bolcaPinserpiştirilmiş gibi hissettiriyorKapsamlı fiber’lar çalıştıramadığınız için sonunda her yere
Arciliştirmek zorunda kalıyorsunuz;Piniseunsafeolmadan kullanması zor ve asenkron bir fonksiyondaki çok küçük bir değişiklik, tüm kod tabanındaki future’ları!Sendhâline getirebiliyorasynczorluğunu çözebilir gibi. Nasıl gelişeceğini görmemiz gerekecekAsync Everything kötü bir dil
async/await, JavaScript’te düzgün engelleyici (blocking) thread’ler olmaması sorununu düzeltmeye çalışan korkunç bir fikirdi ve şimdi her dile eklemleniyor. Dili ve kütüphane ekosistemini ikiye bölüp daha uzun süre acı yaratacakJavaScript dışında çoklu iş parçacığıyla çalışmış herkes, aktörlerin veya iletişim kuran sıralı süreçlerin çoklu iş parçacığı için en iyi yöntem olduğunu bilir
Joe Armstrong’un tezinde de çok iş parçacıklı programları anlamanın tek yolunun her thread için katı biçimde sıralı kod yazmak ve birden çok thread’in kodunu tek bir yerde birbirine karıştırmamak olduğu anlatılıyor. Sorundaki gerçek eşzamanlı etkinliklerden biri, programlama dilindeki eşzamanlı süreçlerden birine tam olarak karşılık gelmeli ki kavramsal boşluk en aza insin: https://erlang.org/download/armstrong_thesis_2003.pdf
Java’nın Project Loom’unu uygulayan Ron Pressler’ın
async/awaiteleştirisi de iyi: https://www.youtube.com/watch?v=oNnITaBseYQJavaScript’in kendisine karşı epey ikircikliydi; asıl hedefi
epoll()G/Ç olay döngüsünü insanın gözünü oymak istemeyeceği bir soyutlamayla ele almanın yolunu bulmaktı. Ondan önce başka birçok yöntemi de denemiştiasync/awaitaslında JavaScript’te değil, C#’ta başladıC#’tan Anders Hejlsberg TypeScript’i de yaptı; TypeScript’teki sınıflar, ok fonksiyonları,
async/awaitgibi özellikler sonunda ES6+’a girdiTek iş parçacıklı olay döngüsüne sahip JS/TS için harika bir çözüm olduğunu düşünüyorum. Ama dil ne kadar düşük seviyeliyse bir soyutlama olarak o kadar kötüleşiyor; bu yüzden buradan çıkan
asyncRust eleştirilerinin çoğu yerindeYazı,
asyncRust’ın karmaşıklığını ve zorluğunu iyi açıklıyor; ancak Rust’ın temel felsefelerinden birinin performanstan ödün vermeden bellek güvenliği olduğunu da unutmamak önemliRust’ın asenkron kalıpları, özellikle derleyicinin veri güvenliğini garanti etmesini sağlama biçimi, bu felsefeyi iyi gösteriyor. Karmaşıklık var, ama geliştiriciyi veri ve yürütme akışı üzerine derin düşünmeye iten daha güvenli bir eşzamanlılık modeli olma değeri de var
Rust tüm büyük ölçekli eşzamanlı kullanıcı alanı uygulamaları için doğru yanıt olmayabilir; fakat sağlamlık ve güvenliğin en öncelikli olduğu sistemlerde bu ödünleşim haklı görülebilir. Ekosistem geliştikçe bu acıları azaltacak soyutlamaların ve kütüphanelerin daha da ortaya çıkması muhtemel
asynctabanlı, kilitsiz Rust’ı çok yazıyorum. Başlıca sorun Tokio future’larının'staticolması; bu da Rust ekosisteminin derinlerine işlemiş bir tasarım hatasından, yani bellek sızıntısının güvenli olduğu kararından kaynaklanıyorBu yüzden future’ın düzgün temizleneceğini statik olarak garanti edemiyorsunuz. Bir asenkron görev oluşturulduğunda biri
std::mem::forgetile future’ı unutursa, ödünç alma denetleyicisi o future’ın geçişli olarak aktardığı referansların hâlâ canlı olduğunu bilemezArc’ı her yere serpiştirmek yerine şuunsafecrate’i kullanıyorum: https://docs.rs/async-scoped/latest/async_scoped/Bu kadarı C++’ta yapacağım hataların %99’unu yakaladığı için makul bir ödünleşim. Güvenli biçimde
'staticolmayan future uygulama çalışması da sürüyor; umarım başarılı olurBir diğer büyük sorun da
async traitin şu anda kutulanmış future gerektirmesi ve bunun her fonksiyon çağrısı sınırındamalloc/freeeklemesi; bu yılki düzeltme yol haritasında“Sadece kanal kullanın” tavsiyesi de büyük kod tabanlarında kontrol akışını her yana dağıtıyor. Kanallar modern bir
GOTOgibi hissettiriyor; ben de kullanıyorum ama sadece birkaç işi paralel çalıştırıp sonra tamamlanmalarını beklediğim durumlarda pek kullanmıyorum'staticdeğil; çalışma zamanının eşzamanlılığından yararlanarakspawnedilebilen future’ların yalnızca'staticolmasıFuture’ın
poll()edilebilmesi içinPinlenmesi gerekir vePinlenmişT: !Unpineninde sonundaDropçağırmak zorundadır: https://doc.rust-lang.org/std/pin/#drop-guaranteeDerleyicinin
asyncözelliğiyle üretilen future’lar bu niteliğe sahiptir; elle yazılmış future’lara daPhantomPinnedeklenebilir. Bu sayedepoll()edildikten sonramem::forgetile oynanmasını tanımsız davranış olarak varsaymak mümkün olur; intrusive ve kendine referanslı future kütüphaneleri de mümkün hale gelir: https://docs.rs/futures-intrusive/latest/futures_intrusive/Future,
Arc/Rctarafından canlı tutulup sızdırılabilir; ama kütüphane geliştiricisi açısından bu, normal kullanımdan makul biçimde ayırt edilemez ya da çok da önemsenmesi gerekmezRc’yi kaldırmayı mı, yoksa bulaşıcıunsafetrait sınırları eklemeyi mi tercih ederdiniz, merak ediyorum