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ı?
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ı?
Yanıtlar:
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.
İş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.
İ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ı:
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.
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 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.
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 await
bunun içinde. Bu temelde, loop
daha sonra farklı bir işlevin çalışmasına izin veren (bu durumda farklı bir factorial
iş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