Yığın yapısı zaman uyumsuz işlemler için kullanılıyor mu?


10

Bu soru Eric Lippert'in yığının ne için kullanıldığını açıklayan mükemmel bir cevabı var. Yıl boyunca bildiğim - genel olarak konuşursak - yığının ne olduğunu ve nasıl kullanıldığını, ancak cevaplarının bir kısmı, bu yığın yapısının günümüzde asenkron programlamanın norm olduğu daha az kullanılıp kullanılmadığını merak ediyor.

Cevabından:

yığın, sürekliliklerin, coututinleri olmayan bir dilde yeniden birleşmesinin bir parçasıdır.

Özellikle, bunun coroutines olmadan bölümünü merak ediyorum.

Burada biraz daha açıklıyor:

Koroutinler, nerede olduklarını hatırlayabilen, bir süre başka bir koroutine kontrol verebilen ve daha sonra kaldıkları yerden devam edebilecekleri, ancak koroutin verimlerden hemen sonra değil, devam edebilecek işlevlerdir. Bir sonraki öğe istendiğinde veya eşzamansız işlem tamamlandığında nerede olduklarını hatırlaması gereken C # 'da "geri dönüş" veya "bekle" yi düşünün. Koroutinleri veya benzer dil özelliklerine sahip diller, sürekliliği uygulamak için bir yığından daha gelişmiş veri yapıları gerektirir.

Bu, yığın açısından mükemmel, ancak bir yığın daha gelişmiş veri yapıları gerektiren bu dil özelliklerini işlemek için çok basit olduğunda hangi yapının kullanıldığı hakkında bana cevapsız bir soru bırakıyor?

Teknoloji ilerledikçe yığın kayboluyor mu? Ne yerini alıyor? Karma bir şey mi? (örneğin, .NET programım bir zaman uyumsuz çağrıyı tamamlayana kadar bir yığın kullanıyor mu ve tamamlanıncaya kadar başka bir yapıya geçiyor, bu noktada yığının bir sonraki öğeden emin olabileceği bir duruma geri döndürülüyor mu? )

Bu senaryoların bir yığın için çok gelişmiş olması mükemmel bir anlam ifade eder, ancak yığının yerini ne alabilir? Bu yıllar önce öğrendiğimde, yığın şimşek hızlı ve hafif olduğu için oradaydı, ödevden uzak uygulamada tahsis edilen bir bellek parçası, çünkü eldeki görev için son derece verimli yönetimi destekledi (pun amaçlı?). Ne değişti?

Yanıtlar:


14

Karma bir şey mi? (örneğin, .NET programım bir zaman uyumsuz çağrıyı tamamlayana kadar bir yığın kullanıyor mu ve tamamlanıncaya kadar başka bir yapıya geçiyor, bu noktada yığının bir sonraki öğeden emin olabileceği bir duruma geri döndürülüyor? )

Temel olarak evet.

Varsayalım

async void MyButton_OnClick() { await Foo(); Bar(); }
async Task Foo() { await Task.Delay(123); Blah(); }

İşte devamların nasıl birleştirildiğine dair son derece basitleştirilmiş bir açıklama. Gerçek kod çok daha karmaşıktır, ancak bu fikri tersine çevirir.

Düğmesini tıklayın. Bir mesaj kuyruğa alındı. İleti döngüsü iletiyi işler ve ileti sırasının dönüş adresini yığına koyarak tıklama işleyicisini çağırır. Yani, işleyici yapıldıktan sonra olan şey , mesaj döngüsünün çalışmaya devam etmesi gerektiğidir. Dolayısıyla, işleyicinin devamı döngüdür.

Tıklama işleyici, Foo () öğesini çağırır ve dönüş adresini yığına yerleştirir. Yani, Foo'nun devamı tıklama işleyicinin geri kalan kısmıdır.

Foo, Task.Delay öğesini çağırır ve dönüş adresini yığına koyar.

Görev: Gecikme, bir Görevi hemen geri döndürmek için ne gerekiyorsa yapsın. Yığın attı ve biz de Foo'ya geri döndük.

Foo, döndürülen görevi tamamlayıp tamamlamadığını kontrol eder. O değil. Devamı ait beklemektedir Foo görevinin devamı olarak yukarı temsilci olduğunu Blah (çağıran bir temsilci) oluşturur ve işaretler, böylece) Blah (aramak. (Az önce yanlış bir açıklama yaptım; yakaladın mı? Değilse, bir an içinde ortaya koyacağız.)

Foo daha sonra kendi Görev nesnesini oluşturur, eksik olarak işaretler ve yığını tıklama işleyicisine döndürür.

Tıklama işleyici Foo'nun görevini inceler ve eksik olduğunu keşfeder. İşleyicideki beklemenin devamı Bar () öğesini çağırmaktır, bu nedenle tıklama işleyicisi Bar () öğesini çağıran ve onu Foo () tarafından döndürülen görevin devamı olarak ayarlayan bir temsilci oluşturur. Daha sonra yığını ileti döngüsüne döndürür.

Mesaj döngüsü mesajları işlemeye devam eder. Sonunda, gecikme görevi tarafından oluşturulan zamanlayıcı büyüsü işini yapar ve kuyruğa gecikme görevinin devamının yürütülebileceğini söyleyen bir mesaj gönderir. Bu yüzden mesaj döngüsü görev devamını çağırır ve kendisini her zamanki gibi yığının üzerine koyar. Bu delege Blah () diyor. Blah () yaptığı işi yapar ve yığını geri döndürür.

Şimdi ne olacak? İşte biraz zor. Gecikme görevinin devamı sadece Blah'ı () çağırmaz. Ayrıca Bar () çağrısını da tetiklemek zorundadır , ancak bu görev Bar'ı bilmiyor!

Foo aslında (1) Blah'ı () çağıran ve (2) Foo'nun yarattığı ve olay işleyicisine geri verdiği görevin devamını çağıran bir temsilci yarattı. Bar () diyen bir delege diyoruz.

Ve şimdi yapmamız gereken her şeyi doğru sırada yaptık. Ancak mesaj döngüsünde mesajları çok uzun süre işlemeyi hiç bırakmadık, bu yüzden uygulama duyarlı kaldı.

Bu senaryoların bir yığın için çok gelişmiş olması mükemmel bir anlam ifade eder, ancak yığının yerini ne alabilir?

Delegelerin kapanış sınıfları yoluyla birbirlerine referanslar içeren görev nesnelerinin grafiği. Bu kapatma sınıfları , en son idam edilenlerin konumunu ve yerlilerin değerlerini takip eden devlet makineleridir . Ayrıca, verilen örnekte, işletim sistemi tarafından uygulanan global durum eylem sırası ve bu eylemleri yürüten ileti döngüsü.

Alıştırma: Tüm bunların mesaj döngüleri olmayan bir dünyada nasıl çalıştığını düşünüyorsunuz? Örneğin, konsol uygulamaları. bir konsol uygulamasında beklemek oldukça farklı; nasıl çalıştığını şimdiye kadar bildiklerinizden çıkarabilir misiniz?

Bu yıllar önce öğrendiğimde, yığın şimşek hızlı ve hafif olduğu için oradaydı, eldeki görev için son derece verimli yönetimi desteklediği için yığıntan uygulamada ayrılmış bir bellek parçası (pun amaçlı?). Ne değişti?

Yığınlar, yöntem aktivasyonlarının ömürleri bir yığın oluşturduğunda yararlı bir veri yapısıdır, ancak benim örneğimde tıklama işleyicisinin, Foo, Bar ve Blah'ın aktivasyonları bir yığın oluşturmaz. Dolayısıyla bu iş akışını temsil eden veri yapısı bir yığın olamaz; bunun yerine, bir iş akışını temsil eden yığınlara ayrılmış görevlerin ve temsilcilerin bir grafiğidir. Beklenenler, iş akışında, daha önce başlatılan iş tamamlanana kadar iş akışında daha fazla ilerleme kaydedilemeyen noktalardır; biz beklerken, tamamlanan belirli görevlere bağlı olmayan diğer işleri yürütebiliriz .

Yığın, yalnızca karelerden oluşan bir dizidir; burada kareler, işlevlerin ortasına (çağrının gerçekleştiği yer) işaretçiler (()) ve yerel değişkenlerin ve temps değerlerini içerir. Görevlerin devamı aynı şeydir: temsilci işleve bir göstericidir ve işlevin ortasında (beklemenin gerçekleştiği yerde) belirli bir noktaya başvuran bir duruma sahiptir ve kapanışın her yerel değişken veya geçici için alanları vardır . Çerçeveler artık güzel ve düzgün bir dizi oluşturmuyor, ancak tüm bilgiler aynı.


1
Çok faydalı, teşekkürler. Her iki yanıtı da kabul edilmiş bir cevap olarak işaretleyebilseydim, ama boş bırakamayacağım için (ama kimsenin cevaplama zamanının takdir edilmediğini düşünmesini istemedim)
jleach

3
@ jdl134679: Sorunuzun yanıtlandığını düşünüyorsanız bir şeyi yanıt olarak işaretlemenizi öneririm; insanların cevap yazmak yerine iyi bir cevap okumak istiyorlarsa buraya gelmeleri gerektiğine dair bir sinyal gönderir . (Tabii ki iyi cevaplar yazmak her zaman teşvik edilir.) Onay işaretini kimin aldığını umursamıyorum.
Eric Lippert

8

Appel, eski bir kağıt çöp toplama yığını tahsis daha hızlı olabilir yazdı . Aynı zamanda onun Oku continuations ile derleme kitap ve çöp toplama el kitabında . Bazı GC teknikleri (istemeden) çok verimlidir. Devamı geçen tür , bir kanonik tanımlayan bütün program dönüşümü (CPS dönüşüm yığınlar (kavramsal yığın ayrılmış çağrı çerçeveler yerine kurtulmak) kapakları birey "değerleri" gibi tek tek çağrı çerçeveler veya "nesneler" "şeyleştirici", diğer bir deyişle ).

Ancak çağrı yığını hala çok yaygın olarak kullanılmaktadır ve mevcut işlemciler çağrı yığınlarına adanmış özel donanıma (yığın kaydı, önbellek makineleri, ...) sahiptir ve bu nedenle çoğu düşük seviyeli programlama dilinin, özellikle de C, çağrı yığını ile uygulayın). Ayrıca, yığınların önbellek dostu olduğuna ve performans için çok önemli olduğuna dikkat edin .

Pratik olarak, çağrı yığınları hala burada. Ancak şimdi birçoğumuz var ve bazen çağrı yığını, bazen çöp toplanan veya öbek atanan birçok daha küçük segmente (örneğin, her biri birkaç MBbayt) bölünüyor. Bu yığın segmentleri, bazı bağlantılı listelerde (veya gerektiğinde daha karmaşık veri yapılarında) düzenlenebilir. Örneğin, GCC derleyiciler var bir -fsplit-stackseçenek (Go ve onun "goroutines" ve onun "zaman uyumsuz süreçlerinde" için özellikle faydalıdır). Bölünmüş yığınlarla, milyonlarca küçük yığın segmentinden yapılmış binlerce yığının (ve yardımcı programların uygulanması daha kolay hale gelebilir) olabilir ve yığın "gevşetmek", yığının daha hızlı (veya en azından neredeyse tek bir yığınla olduğu kadar hızlı) yığın).

(diğer bir deyişle, yığın ve yığın arasındaki ayrım bulanıktır, ancak tüm program dönüşümü veya çağrı kuralını ve derleyiciyi uyumsuz olarak değiştirmeyi gerektirebilir )

Ayrıca bkz bu & o ve birçok kağıtları (örneğin bu ) tartışırken CPS dönüşümü. ASLR & call / cc hakkında da okuyun . Okuma (& STFW) hakkında daha fazla continuations .

.CLR ve .NET uygulamaları, birçok pragmatik nedenden ötürü en son teknoloji GC & CPS dönüşümüne sahip olmayabilir. Tüm program dönüşümleriyle ilişkili bir değiş tokuş (ve düşük seviyeli C rutinlerini kullanma kolaylığı ve C veya C ++ ile kodlanmış bir çalışma süresine sahip olma).

Tavuk Şeması , makine (veya C) yığınını CPS dönüşümü ile alışılmadık bir şekilde kullanıyor: her ayırma yığın üzerinde gerçekleşiyor ve çok büyük hale geldiğinde son yığın tahsis edilen değerleri (ve muhtemelen akım devam) ve yığın büyük ölçüde azaltılır setjmp.


Ayrıca okuyun SICP , Programlama Dili Edimbilim , Ejderha Kitabı , Lisp Küçük Parçalar .


1
Çok faydalı, teşekkürler. Her iki yanıtı da kabul edilmiş bir cevap olarak işaretleyebilseydim, ama boş bırakamayacağım için (ama kimsenin cevaplama zamanının takdir edilmediğini düşünmesini istemedim)
jleach
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.