Coroutines neden geri döndü? [kapalı]


19

Coroutines için temelin çoğu 60'larda / 70'lerde meydana geldi ve daha sonra alternatifler lehine durdu (örneğin, iplikler)

Python ve diğer dillerde meydana gelen koroutinlere yeniden ilgi duyulan herhangi bir madde var mı?



9
Gittiklerinden emin değilim.
Blrfl

Yanıtlar:


26

Coroutines hiç ayrılmadı, bu arada diğer şeyler tarafından gölgede bırakıldı. Son zamanlarda eşzamansız programlamaya ve dolayısıyla koroutinlere artan ilgi büyük ölçüde üç faktörden kaynaklanmaktadır: fonksiyonel programlama tekniklerinin artan kabulü, gerçek paralelliğe zayıf desteği olan araçlar (JavaScript! Python!) Ve en önemlisi: iş parçacıkları ve koroutinler arasındaki farklı dengesizlikler. Bazı kullanım durumlarında, eş-nesneler objektif olarak daha iyidir.

80'lerin, 90'ların ve bugünün en büyük programlama paradigmalarından biri OOP. OOP tarihine ve özellikle Simula dilinin gelişimine bakarsak, sınıfların koroutinlerden evrimleştiğini görürüz. Simula, kesikli olaylara sahip sistemlerin simülasyonu için tasarlandı. Sistemin her bir elemanı, bir simülasyon aşaması süresince olaylara yanıt olarak yürütülecek, daha sonra diğer süreçlerin işlerini yapmasına izin verecek ayrı bir süreçti. Simula 67'nin geliştirilmesi sırasında sınıf konsepti tanıtıldı. Şimdi, eşbürenin kalıcı durumu nesne üyelerinde saklanır ve olaylar bir yöntem çağrılarak tetiklenir. Daha fazla ayrıntı için makaleyi okumayı düşünün Nygaard & Dahl tarafından SIMULA dillerinin geliştirilmesi .

Komik bir bükülme ile birlikte coroutines kullanıyorduk, onlara sadece nesneler ve olay güdümlü programlama diyorduk.

Paralelliğe ilişkin olarak, iki tür dil vardır: uygun bir bellek modeline sahip olanlar ve olmayanlar. Bir bellek modeli, “Bir değişkene yazarsam ve bundan sonra başka bir evrede bu değişkenten okursam, eski değeri mi yoksa yeni değeri mi, yoksa geçersiz bir değeri mi görüyorum? 'Önce' ve 'sonra' ne anlama geliyor? Hangi operasyonların atomik olacağı garanti ediliyor? ”

İyi bir bellek modeli oluşturmak zordur, bu nedenle bu çaba, bu tanımlanmamış, uygulama tanımlı dinamik açık kaynak dillerinin çoğu için hiç yapılmamıştır: Perl, JavaScript, Python, Ruby, PHP. Tabii ki, tüm bu diller başlangıçta oluşturuldukları “betiklemenin” çok ötesine geçmiştir. Bu dillerin bazılarında bir çeşit bellek modeli belgesi var, ancak bunlar yeterli değil. Bunun yerine, saldırılarımız var:

  • Perl, diş çekme desteği ile derlenebilir, ancak her bir iş parçacığı, yorumlayıcı durumunun ayrı bir klonunu içerir, bu da iş parçacıklarını çok pahalı hale getirir. Tek faydası olarak, bu paylaşılan-hiçbir şey yaklaşımı veri yarışlarını önler ve programcıları yalnızca kuyruklar / sinyaller / IPC aracılığıyla iletişim kurmaya zorlar. Perl'in asenkron işleme için güçlü bir hikayesi yok.

  • JavaScript'in fonksiyonel programlama için her zaman zengin desteği vardır, bu nedenle programcılar eşzamansız işlemlere ihtiyaç duydukları programlarda devamları / geri çağrıları manuel olarak kodlarlar. Örneğin, Ajax istekleri veya animasyon gecikmeleri ile. Web doğası gereği zaman uyumsuz olduğundan, çok sayıda zaman uyumsuz JavaScript kodu vardır ve tüm bu geri çağrıları yönetmek son derece acı vericidir. Bu nedenle, bu geri çağrıları daha iyi organize etmek (Vaatler) veya tamamen ortadan kaldırmak için birçok çaba görüyoruz.

  • Python, Global Tercüman Kilidi adı verilen bu talihsiz özelliğe sahiptir. Temel olarak Python bellek modeli “Paralellik olmadığı için tüm efektler sırayla görünür. Bir kerede yalnızca bir iş parçacığı Python kodunu çalıştırır. ”Python'un iş parçacığı olsa da, bunlar yalnızca couteinler kadar güçlüdür. [1] Python, jeneratör fonksiyonları ile birçok coutini kodlayabilir yield. Düzgün kullanılırsa, bu tek başına JavaScript'ten bilinen geri arama cehenneminin çoğunu önleyebilir. Python 3.5'in daha yeni eşzamansız / bekleme sistemi, eşzamansız deyimleri Python'da daha kullanışlı hale getirir ve bir olay döngüsünü entegre eder.

    [1]: Teknik olarak bu kısıtlamalar sadece Python referans uygulaması olan CPython için geçerlidir. Jython gibi diğer uygulamalar, paralel olarak yürütülebilen, ancak eşdeğer davranış uygulamak için çok uzun süre geçmesi gereken gerçek iş parçacıkları sunar. Temel olarak: her değişken veya nesne elemanı uçucu bir değişkendir, böylece tüm değişiklikler atomiktir ve hemen tüm evrelerde görülür. Elbette, değişken değişkenlerin kullanılması normal değişkenlerin kullanılmasından çok daha pahalıdır.

  • Onları düzgün kızartmak için Ruby ve PHP hakkında yeterli bilmiyorum.

Özetlemek gerekirse: bu dillerin bazılarında, çoklu iş parçacığını istenmeyen veya imkansız hale getiren temel tasarım kararları vardır, bu da coroutines gibi alternatiflere ve asenkron programlamayı daha uygun hale getirmenin yollarına daha fazla odaklanmaya yol açar.

Son olarak, coroutines ve threadlar arasındaki farklar hakkında konuşalım:

İş parçacıkları temel olarak işlemlere benzer, ancak işlem içindeki birden çok iş parçacığı bir bellek alanını paylaşır. Bu, yivlerin bellek açısından hiçbir şekilde “hafif” olmadığı anlamına gelir. İş parçacıkları işletim sistemi tarafından önceden etkin olarak zamanlanır. Bu, görev anahtarlarının yüksek bir ek yüke sahip olduğu ve uygunsuz zamanlarda ortaya çıkabileceği anlamına gelir. Bu ek yükün iki bileşeni vardır: iş parçacığının durumunu askıya alma maliyeti ve kullanıcı modu (iş parçacığı için) ve çekirdek modu (zamanlayıcı için) arasında geçiş yapma maliyeti.

Bir işlem kendi iş parçacıklarını doğrudan ve işbirliği içinde zamanlarsa, çekirdek moduna bağlam geçişi gereksizdir ve görevleri değiştirmek dolaylı bir işlev çağrısı için nispeten pahalıdır: oldukça ucuz. Bu hafif iplikler, çeşitli detaylara bağlı olarak yeşil iplikler, lifler veya koroutinler olarak adlandırılabilir. Yeşil ipliklerin / liflerin dikkate değer kullanıcıları erken Java uygulamaları ve daha yakın zamanda Golang'daki Goroutines'ti. Koroutinlerin kavramsal bir avantajı, bunların uygulanmasının, koroutinler arasında açıkça ileri geri hareket eden kontrol akışı açısından anlaşılabilmesidir. Bununla birlikte, bu koroutinler, birden fazla işletim sistemi iş parçacığı arasında programlanmadığı sürece gerçek paralelliğe ulaşmazlar.

Ucuz koroutinler nerede yararlıdır? Çoğu yazılım bir gazillion iş parçacığına ihtiyaç duymaz, bu nedenle normal pahalı iş parçacıkları genellikle iyidir. Ancak, zaman uyumsuz programlama bazen kodunuzu basitleştirebilir. Serbestçe kullanılabilmesi için bu soyutlamanın yeterince ucuz olması gerekir.

Ve sonra web var. Yukarıda belirtildiği gibi, ağ doğal olarak eşzamansızdır. Ağ istekleri uzun zaman alır. Birçok web sunucusu, çalışan iş parçacıklarıyla dolu bir iş parçacığı havuzu bulundurur. Ancak, çoğu zaman bu iş parçacıkları boşta kalacak, çünkü bir dosyayı diskten yüklerken bir G / Ç olayı beklerken, istemci yanıtın bir bölümünü onaylayana kadar veya bir veritabanına kadar beklerken sorgu tamamlanır. NodeJS, olay tabanlı ve zaman uyumsuz bir sunucu tasarımının son derece iyi çalıştığını olağanüstü bir şekilde göstermiştir. Açıkçası JavaScript web uygulamaları için kullanılan tek dil olmaktan uzaktır, bu nedenle diğer diller için (Python ve C # 'da fark edilir) eşzamansız web programlamayı kolaylaştırmak için büyük bir teşvik vardır.


İntihal riskinden kaçınmak için dördüncü-son paragrafınızı almanızı öneririm, neredeyse okuduğum başka bir kaynakla aynı. Buna ek olarak, iş parçacıklarından daha küçük büyüklük sıralarına sahip olmakla birlikte, programların performansı "dolaylı bir işlev çağrısı" ile basitleştirilemez. Bkz . Burada ve burada coroutine uygulamalarıyla ilgili ayrıntıları artırır .
whn

1
@snb Önerilen düzenlemenizle ilgili olarak: GIL bir CPython uygulama ayrıntısı olabilir, ancak temel sorun Python dilinin verilerin paralel mutasyonunu belirten açık bir bellek modeline sahip olmamasıdır. GIL, bu sorunları ortadan kaldırmak için bir hack'tir. Ancak, gerçek paralelliğe sahip Python uygulamaları, örneğin Jython kitabında tartışıldığı gibi eşdeğer anlambilim sağlamak için büyük çaba sarf etmelidir . Temel olarak: her değişken veya nesne alanı pahalı bir değişken değişken olmalıdır.
amon

3
@snb İntihal ile ilgili: İntihal, fikirleri özellikle akademik bağlamda yanlış bir şekilde kendiniz gibi sunar. Bu ciddi bir iddia , ama eminim öyle demek istemedin. “İş parçacıkları temel olarak süreçler gibidir” paragrafı, işletim sistemleri hakkında herhangi bir derste veya ders kitabında öğretildiği gibi sadece iyi bilinen gerçekleri yineler. Bu gerçekleri kısaca ifade etmenin çok fazla yolu olduğundan, paragrafın size tanıdık geldiğine şaşırmadım.
amon

Ben Python ima etmek anlamını değişmedi mi bir bellek modeli var. Ayrıca, uçucu madde kullanımı, kendi başına performans düşüşünü azaltmaz . Jython dünyasında bu gerçekten önemli olabilir, çünkü VM JIT derlemesini kullanacaktır, ancak CPython dünyasında JIT optimizasyonu hakkında endişelenmiyorsunuz, değişken değişkenleriniz yorumlama çalışma alanında, optimizasyon yapılamayacaktı .
whn

7

İşletim sistemleri önleyici zamanlama yapmadığından, ortak programlar kullanışlıdır . Önleyici zamanlama sağlamaya başladıktan sonra, programınızda periyodik olarak kontrolden çıkmak gerekiyordu.

Çok çekirdekli işlemciler daha yaygın hale geldikçe, görev paralelliğini elde etmek ve / veya bir sistemin kullanımını yüksek tutmak için koroutinler kullanılır (bir yürütme iş parçacığının bir kaynak üzerinde beklemesi gerektiğinde, bir başkası yerinde çalışmaya başlayabilir).

NodeJS, yardımcı programların kullanıldığı özel bir durumdur ve IO'ya paralel erişim sağlar. Yani, G / Ç isteklerine hizmet vermek için birden çok iş parçacığı kullanılır, ancak javascript kodunu yürütmek için tek bir iş parçacığı kullanılır. Bir oturum açma iş parçacığında bir kullanıcı kodu yürütmenin amacı, muteks kullanma gereğini ortadan kaldırmaktır. Bu, yukarıda belirtildiği gibi sistemin kullanımını yüksek tutmaya çalışma kategorisine girer.


4
Ancak eşbütünler işletim sistemi tarafından yönetilmez. OS, C + + liflerinin aksine, bir koroutinin ne olduğunu bilmiyor
aşırı değişim

Birçok işletim sisteminde couteinler vardır.
Jörg W Mittag

python ve Javascript ES6 + gibi coroutines çok işlemli değil mi? Bunlar görev paralelliğine nasıl ulaşır?
whn

1
@Mael Coroutines'ın son "canlanması", her ikisi de anladığım kadarıyla coroutines'leriyle paralellik elde etmeyen python ve javascript'ten geliyor. Yani görev parrallizmi, eşgüdümlerin “geri” olmasının nedeni olmadığı için bu cevabın yanlış olduğu anlamına gelir. Ayrıca Luas da çoklu işlem değil mi? DÜZENLEME: Az önce paralellikten bahsetmediğini fark ettim, ama neden ilk başta bana cevap verdin? Bu konuda yanlış oldukları için dlasalle'a cevap verin.
whn

3
@dlasalle Hayır, herhangi bir kodun aynı anda fiziksel olarak çalıştırıldığı anlamına gelmeyen "paralel olarak çalışıyor" yazmasına rağmen yapamazlar. GIL bunu durduracak ve zaman uyumsuzluk, CPython'da (ayrı GIL'ler) çoklu işlem için gerekli ayrı işlemleri üretmez. Async, tek bir iş parçacığında elde edilen verimle çalışır. Onlar "Paraleli" diğer işlevleri işe yeilding ve aslında ortalama birkaç fonksiyonlar derken interleving fonksiyonlu kullanıma. Python zaman uyumsuz işlemler olamaz çünkü impl ait paralel olarak çalıştırılabilir. Artık parralel coroutines (Lua, Javascript ve Python) yapmayan üç dilim var.
whn

5

İlk sistemler, eşzamanlılık sağlamak için ortak programlar kullandılar çünkü bunlar bunu yapmanın en basit yoluydu. İş parçacıkları işletim sisteminden oldukça fazla destek gerektirir (bunları kullanıcı düzeyinde uygulayabilirsiniz, ancak sistemin sürecinizi periyodik olarak kesmesi için bir yol ayarlamanız gerekir) ve desteğe sahip olduğunuzda bile uygulanması daha zordur .

Konular daha sonra devralmaya başladı çünkü 70'ler veya 80'ler boyunca tüm ciddi işletim sistemleri onları destekledi (ve 90'lar, hatta Windows!) Ve daha genel. Ve kullanımı daha kolaydır. Aniden herkes bir sonraki büyük konu olduğunu düşündü.

90'lı yılların sonlarında çatlaklar ortaya çıkmaya başlamıştı ve 2000'lerin başında ipliklerde ciddi sorunların olduğu ortaya çıktı:

  1. çok fazla kaynak tüketiyorlar
  2. bağlam anahtarları göreceli olarak çok zaman alır ve genellikle gereksizdir
  3. referansların yerini yok ederler
  4. özel erişim gerektirebilecek birden fazla kaynağı koordine eden doğru kod yazmak beklenmedik bir şekilde zor

Zamanla, programların tipik olarak herhangi bir zamanda gerçekleştirmesi gereken görev sayısı hızla artmakta ve yukarıdaki (1) ve (2) 'nin neden olduğu sorunları arttırmaktadır. İşlemci hızı ile bellek erişim süreleri arasındaki eşitsizlik artmakta ve bu da sorunu daha da artırmaktadır (3). Ve programların kaç tane ve ne tür farklı kaynaklara ihtiyaç duydukları açısından karmaşıklığı artıyor ve sorunun ilgisini artırıyor (4).

Ancak, biraz genelliği kaybederek ve programcıların süreçlerinin nasıl birlikte çalışabileceğini düşünmek için biraz fazladan bir hata yaparak, koroutinler tüm bu sorunları çözebilir.

  1. Ortak programlar, yığın için bir avuç sayfadan biraz daha fazla kaynak gerektirir, iş parçacıklarının çoğundan daha az kaynak gerektirir.
  2. Eşgüdümler, yalnızca programcı tanımlı noktalarda bağlam değiştirir, bu da yalnızca gerektiğinde anlamına gelir. Ayrıca genellikle iş parçacıkları kadar bağlam bilgisini (örneğin kayıt değerleri) korumasına gerek yoktur, yani her anahtar genellikle daha hızlıdır ve daha azına ihtiyaç duyar.
  3. Üretici / tüketici tipi operasyonlar da dahil olmak üzere yaygın koroutin kalıpları, rutinler arasında konumun aktif olarak artacağı şekilde verileri dağıtır. Dahası, bağlam anahtarları tipik olarak yalnızca kendi içinde olmayan iş birimleri arasında, yani yerin genellikle en aza indirildiği bir zamanda gerçekleşir.
  4. Rutinler bir işlemin ortasında keyfi olarak kesintiye uğrayamayacaklarını bildiklerinde, daha basit uygulamaların düzgün çalışmasına olanak tanıdığında, kaynak kilidinin gerekli olması daha az olasıdır.

5

önsöz

Ben eşyordamlar bir neden belirterek belirterek ait başlamak istiyorum değildir , paralellik bir canlanma oluyor. Genelde Modern değiş tokuş eden kavramlar olarak vardır değil , modern uygulamalar çoklu işlem işlevselliği kullanmak yok gibi, görev bazlı paralellik sağlamak için bir araç. Buna en yakın şey lif gibi şeyler .

Modern Kullanım (neden geri döndüler)

Modern coroutines, haskell gibi fonksiyonel dillerde çok yararlı bir şey olan tembel bir değerlendirme elde etmenin bir yolu olarak geldi , burada bir işlemi gerçekleştirmek için tüm bir seti yinelemek yerine, yalnızca gerektiği kadar bir işlem değerlendirmesi yapabileceksiniz ( sonsuz öğe kümeleri veya erken sonlandırma ve alt kümeleri olan büyük kümeler için yararlıdır).

Oluşturmak için Verim anahtar kelimenin kullanımı ile jeneratörleri , modern uygulanmasında, Python ve C #, değiş tokuş eden kavramlar gibi dillerde (tembel değerlendirme ihtiyaçlarının bir kısmını karşılamak kendileri de) sadece mümkün, ancak mümkün dilin kendisi özel sözdizimi dışarı ile olan (python sonunda yardımcı olmak için birkaç bit ekledi). Düşüncesiyle tembel geçirdikleri anlam ile İşbirliği rutinleri yardım gelecek ler o anda bir değişkenin değerini gerekmiyorsa, size açık olarak bu değerin istemek kadar edinme aslında geciktirebilirsiniz (eğer değeri kullanmak için izin ve temsili örneklemeden farklı bir zamanda tembel olarak değerlendirin ).

Bununla birlikte, tembel değerlendirmenin ötesinde, özellikle web küresinde, bu ortak rutinler geri çağırma cehennemini düzeltmeye yardımcı olur . Ortak programlar, istemci makinedeki işlem süresinin ihtiyacınız olan şeylere daha hızlı erişimle sonuçlanmayacağı veritabanı erişimi, çevrimiçi işlem, kullanıcı arabirimi vb. Diş açma aynı şeyi yerine getirebilir, ancak bu alanda çok daha fazla yük gerektirir ve koroutinlerin aksine, aslında görev paralelliğinde faydalıdır .

Kısacası, web geliştirme büyüdükçe ve fonksiyonel paradigmalar zorunlu dillerle daha fazla birleştikçe, eş-programlar eşzamansız sorunlara ve tembel değerlendirmeye bir çözüm haline gelmiştir. Eşgüdümler, genel olarak çok işlemli diş çekme ve diş açma işlemlerinin gereksiz, uygunsuz veya mümkün olmadığı sorunlu alanlara gelir.

Modern Örnek

Javascript, Lua, C # ve Python gibi dillerdeki programların tümü, uygulamalarını ana işlevlerin diğer işlevlere (işletim sistemi çağrıları ile ilgisi olmayan) kontrolünü veren bireysel işlevlerle türetir .

Gelen bu piton Örneğin , biz denilen şey ile komik bir piton fonksiyonu var awaitbunun içinde. Bu temelde, loopdaha sonra farklı bir işlevin çalışmasına izin veren (bu durumda farklı bir factorialişlev) yürütme sağlayan bir verimdir . Bir yanlış adlandırma olan "Görevlerin paralel yürütülmesi" yazdığında, aslında paralel olarak yürütülmediğini, await anahtar sözcüğünü kullanarak serpiştirme işlevinin yürütülmesini unutmayın (bu yalnızca özel bir verim türü olduğunu unutmayın)

Onlar izin bekar, sigara paralel için kontrol verimleri eşzamanlı değildir süreçler görev paralel bu görevler faaliyet yok anlamında, hiç aynı anda. Eşyordamlar olan değil , modern dil uygulamalarında iplikler. Tüm bu dillerin ko rutinlerinin uygulanması, bu fonksiyon verim çağrılarından türetilir (programcı aslında ko rutinlerinize manuel olarak koymanız gerekir).

DÜZENLEME: C ++ Boroutine2 aynı şekilde çalışır ve açıklamaları, sarılar ile bahsettiğim şey hakkında daha iyi bir görsel vermelidir, buraya bakın . Gördüğünüz gibi, uygulamalarda "özel bir durum" yoktur, takviye lifleri gibi şeyler kuralın istisnasıdır ve hatta açık senkronizasyon gerektirir.

EDIT2: Birisi c # görev tabanlı sistem hakkında konuştuğumu düşündüğüm için, değildi. Unity'nin sistemi ve saf c # uygulamaları hakkında konuşuyordum


@ T.Sar, C # 'ın herhangi bir "doğal" eşgüdümüne sahip olduğunu, C ++' nın (değişebileceğini) ne python (ne de hala vardı) olduğunu ve üçünün de rutin uygulamaları olduğunu söylemedim. Ancak, koroutinlerin (birliklerdekiler gibi) tüm C # uygulamaları, tarif ettiğim gibi verime dayanmamaktadır. Ayrıca burada "hack" kullanımınız anlamsız, sanırım her program bir hack çünkü her zaman dilde tanımlanmadı. Hiçbir şekilde C # "görev tabanlı sistem" bir şey ile karıştırıyorum, ben bile bahsetmedi.
whn

Cevabınızı biraz daha netleştirmenizi öneririm. C # hem beklemek talimatı kavramına hem de göreve dayalı bir paralellik sistemine sahiptir - C # kullanarak ve bu kelimeleri python'a gerçekten paralel olmadığını nasıl anlatırken çok fazla karışıklığa neden olabilir. Ayrıca, ilk cümlenizi kaldırın - böyle bir cevapta diğer kullanıcılara doğrudan saldırmak gerekmez.
T. Sar - Monica'yı yeniden
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.