Çoklu iş parçacığı karşılaştırması: Haskell, Java ve Python'u yener

Saberie

Active member
Mart 2024 itibarıyla Python, aylar önce olduğu gibi Tiobe Programlama Topluluğu Endeksi'nde birinci sırada yer alırken, Java dördüncü sırada yer alıyor. Haskell daha çok kenarda oynuyor ve ancak 30. sırada bitirebiliyor. Bununla birlikte, çoklu iş parçacıklı kıyaslama testimiz, Haskell'in, en azından rekabet söz konusu olduğunda, adil olmayan bir şekilde göz ardı edildiğini ortaya çıkaracaktır.

Duyuru







(Resim:

Anzela Minosi

)



Anzela Minosi, LibreOffice projeleri oluşturma, C++, Haskell veya Java ile yazılmış yazılımları yeniden derleme ve Raspberry Pi desteği gibi görevlerde serbest olarak çalışıyor. Örnekler Github hesaplarında bulunabilir: github.com/amxyz-cyber







Çoklu iş parçacığının önemi


Programlamada paralel hesaplamalar (paralellik) ile görevlerin aynı anda yürütülmesi (eşzamanlılık veya çoklu iş parçacığı) arasında bir ayrım yapılır. Her iki tekniğin de farklı uygulama alanları vardır: Paralel programlar, donanım kaynaklarının, özellikle de birkaç CPU çekirdeğinin yoğun kullanımı yoluyla hıza ulaşır. Bir program, hesaplamayı tek tek CPU çekirdeklerine devrettiği birden çok parçaya bölerek hedefine daha hızlı ulaşır. Paralel programlama, her şeyden önce, örneğin sıralama algoritmalarına fayda sağlayan hesaplamanın verimliliği ile karakterize edilir.

Bunun aksine, çoklu iş parçacığı veya eşzamanlılık, birden çok iş parçacığının kullanımına dayanan bir teknolojidir. Kavramsal olarak, iş parçacıkları eş zamanlı olarak çalışır; bu da kullanıcıların etkilerinin örtüştüğünü deneyimledikleri anlamına gelir. Aslında hepsinin aynı anda çalışıp çalışmadığı bir uygulama meselesidir. Çoklu iş parçacığı tabanlı bir program, aralıklı yürütme yoluyla tek bir CPU çekirdeği üzerinde yürütülebilir veya birden fazla fiziksel çekirdeğe veya CPU'ya dağıtılabilir. Çoklu iş parçacığı, programın birbirinden bağımsız hareket eden birkaç harici aracıyla etkileşime girmesi gerektiğinde özellikle kullanışlıdır. Örneğin, bir veritabanı sunucusuna birden fazla harici istemci erişimi, çoklu iş parçacığına dayanır. Çoklu iş parçacığının bir diğer avantajı bu tür programların modülerliğidir. Çünkü kullanıcılarla etkileşime giren bir iş parçacığı, veritabanıyla konuşan bir iş parçacığından farklıdır. Çoklu iş parçacığı olmadan, geliştiricilerin olay döngüleri ve geri referanslar içeren istemci/sunucu uygulamaları yazmaları gerekir; bu da çok daha fazla zaman alır. Eşzamanlılık veya genel olarak paralellik uygulamaları, kullanılan mekanizmalar ve ortaya çıkan karmaşıklık açısından dilden dile büyük farklılıklar gösterir (Şekil 1).




Java, Haskell ve Python'un Karmaşıklığı



Çoklu iş parçacığı söz konusu olduğunda Haskell en karmaşık olanı sunar (Şekil 1).


(Fotoğraf: Anzela Minosi)



Python ve GIL olayı


Ocak 2023'te Python Yönlendirme Konseyi'ne isteğe bağlı olarak çoklu iş parçacığını daha zor hale getiren Global Tercüman Kilidi'ni (GIL) sunmak için yapılan PEP 703 teklifi, Python topluluğunda geniş tepkiyle karşılandı. Ve aslında 3.13 sürümüne sahip iki yapı var: standart olan birkaç yıl boyunca GIL ile birlikte gönderilmeye devam edecek, deneysel yapı ise GIL olmadan gelecek.

GIL, Python yorumlayıcısının yalnızca bir iş parçacığı çalıştırmasını sağlar. Bunun tek iş parçacıklı programlarda performans üzerinde olumsuz bir etkisi yoktur, ancak yoğun CPU hesaplamaları veya çoklu iş parçacığı bir rol oynadığında darboğaza dönüşebilir. GIL, Python'da yalnızca bir nedenden dolayı mevcuttur: Python, belleği yönetmek için oluşturulan tüm Python nesneleri için bir referans sayısı kullanır. Bu, bir nesneye işaret eden işaretçileri sayar. Sıfıra ulaşıldığında nesnenin kapladığı hafıza serbest bırakılır. GIL, iki iş parçacığının referans sayısının değerini değiştirdiği yarış koşullarını önlemek için bir erişim bloğu görevi görür. Python kilitlenmeleri bu şekilde önler. GIL, CPU'yu yoğun kullanan herhangi bir programı etkili bir şekilde tek iş parçacıklı bir programa dönüştürür; bu aslında yalnızca bir erişim bloğu gerektiğinden, tek iş parçacıklı programlar için performans artışı anlamına gelir.

Aşağıda, GIL'e rağmen bir Python programının parçalarının paralel işlenmesini uygulamak için iş parçacıkları yerine çoklu işlem süreçlerinin nasıl kullanılabileceğini göstermek için bir örnek kullanacağız. Her işlemin kendi Python yorumlayıcısı ve depolama alanı vardır.

GIL'den kaçınmanın başka alternatifleri de var. Örneğin, standart Python yorumlayıcısı CPython değiştirilebilir: nogil çatalı GIL olmadan çalışır. İş parçacığı kitaplığı daha sonra çok iş parçacıklı programı çalıştırmak için Python modülünüze aktarılabilir. Ancak bu yöntemin dezavantajları da vardır çünkü nogil eski bir Python sürümüne dayanmaktadır. Ek olarak, GIL'e bağlı üçüncü taraf kütüphaneler kolaylıkla kurulamaz veya içe aktarılamaz. Ve tek iş parçacıklı kod daha da yavaş çalışır.

Diğer bir yöntem ise C dilinde yazılmış ve Yabancı Fonksiyon Arayüzü (FFI) aracılığıyla entegre edilen genişletme modülleridir. FFI, C kitaplıkları ile iletişime izin verir. Birden fazla iş parçacığının kullanımına dayanan kod, bir C genişletme modülü olarak dışa aktarılır ve gcc gibi bir C derleyicisi ile derlenir. Sonuç, .so uzantılı paylaşılan bir C kitaplığıdır. Python'da artık iş parçacığı kitaplığıyla birlikte Ctypes kitaplığını içe aktararak birden çok iş parçacığı boyunca bir C işlevini çağırmak mümkün.

Java'da çok iş parçacıklı


Java programlama dili geleneksel olarak Python'dan daha çok iş parçacıklıdır çünkü her Java uygulaması iş parçacıklarına dayanır. Java Sanal Makinesi (JVM) bir kez başlatıldığında, görevlerini (çöp toplama, sonlandırma) gerçekleştirmek için iş parçacıklarının yanı sıra Main yöntemini yürüten bir ana iş parçacığı oluşturur. Ek olarak, JavaFX veya Swing GUI oluşturma kitaplıkları gibi çerçeveler, kullanıcı arayüzü olaylarını işlemek için iş parçacıklarından yoğun şekilde yararlanır. Gecikmeli görevleri gerçekleştirmek üzere iş parçacıkları oluşturmak için zamanlayıcı gibi başka sınıflar da vardır. Java'daki iş parçacığı güvenliği, yöntem çağrılarından ve işletim sisteminin zamanlayıcısından bağımsız olarak bir nesnenin yasa dışı veya tutarsız bir duruma girmesini imkansız hale getirir. Python ile karşılaştırıldığında Java, çok iş parçacıklı uygulamaların iş parçacığı açısından güvenli uygulanmasını sağlayan çeşitli araçlara sahiptir. Örneğin, aynı anda birden fazla iş parçacığı tarafından erişilebilen değişkenler, anahtar kelime kullanılarak tanımlanabilir. volatile iş parçacığı güvenliğini uygulayın. with a'daki bir fonksiyondan önce volatile İşaretlenen değişkene erişilirse değeri ana bellekten okunmalıdır. Aynı şekilde bu değişkeni değiştirdiğinizde Java, yazma işlemi tamamlandıktan sonra değerin ana belleğe geri yazılmasını sağlar. Geçici değişkenler genellikle istemci/sunucu uygulamalarında kapatmaya kadar çalıştırma modellerinin uygulanmasında rol oynar. Bu şekilde, bir istemci, çalışan bir iş parçacığına, iş parçacığının normal şekilde çıkması için halihazırda işlediği işi bitirmesi için sinyal verir.

Ancak bloklar veya yöntemler anahtar kelime kullanılarak oluşturulabilir. synchronized Veri tutarsızlığına karşı koruma. Bu, bir blok veya yöntem içindeki koda erişimi sınırlandırır. Arkasındaki işbirliği mekanizması synchronized gizler, sözde monitörleri erişim engeli olarak kullanır. Bu, her Java nesnesinin sahip olduğu bir belirteçtir. Bir iş parçacığı kilide eriştiğinde monitörü kendisi alır, kilidi çalıştırır ve monitörü tekrar serbest bırakır. Kilide erişmek isteyen bir sonraki iş parçacığı, monitör desteği erişim kilidini serbest bırakana kadar engellenecektir.

Eşzamanlı kod yürütme, derleyicinin ilk sürümünden bu yana var olan Thread sınıfını kullanarak Java'da uygulanabilir. Yürütme birimleri açısından, iş parçacıkları işlemlerden daha hafiftir ve yine de isteğe bağlı Java kodunu çalıştırabilir. İş parçacıkları, adres alanı iş parçacıkları arasında paylaşılan herhangi bir işlemin parçasıdır. Her iş parçacığı, zamanlayıcıdan bağımsız olarak programlanabilir ve kendi yığınına ve program sayacına sahiptir, ancak belleği ve nesneleri aynı süreçte diğer iş parçacıklarıyla paylaşır.

Özetle, Java'nın iş parçacığı modeli, bir süreçteki farklı iş parçacıklarının nesneleri kolayca paylaşmasına olanak tanır; her iş parçacığı, başvurduğu nesneleri değiştirebilir. Ancak geliştiricilerin erişim kilitlerini doğru kullanması her zaman kolay olmuyor ve paylaşılan korunan kaynakların durumu, basit okuma erişimi gibi beklenmedik yerlerde bile oldukça risk altında. Ek olarak, işletim sisteminin zamanlayıcısı o anda hangi iş parçacıklarının çalışmakta olduğuna karar verir.

Haskell'in çoklu iş parçacığına cevabı


Haskell, çoklu iş parçacığı için özel olarak tasarlanmış gibi görünüyor çünkü yaratıcıları olağan kavramları yeniden düşünmüşler. Haskell doğası gereği paraleldir ve iki kategoriye basitleştirilebilecek üç çoklu iş parçacığı biçimine sahiptir. Haskell bir yandan iş parçacığı kullanıyor. Kesin olarak konuşursak, Haskell altındaki bir iş parçacığı, diğer iş parçacıklarından bağımsız olarak çalışan bir IO eylemini temsil eder. Haskell bunun yerine, iş parçacığı tabanlı bir programdan farklı bir şekilde uygulanan paralelliği kullanır.

Haskell altında çoklu iş parçacıklarını uygulamak için kullanılan en yaygın teknikler MVar'lar, Kanallar ve Yazılım İşlemsel Belleğidir (STM). Teknik jargonda MVar, iki iş parçacığı arasında bilgi alışverişine izin veren, iş parçacığı açısından güvenli bir değişkendir. Çalışma sırasında tek bir eleman için bir kutu gibi davranır ve dolu veya boş olabilir. İçine bir şey koyabilir veya ondan bir şey çıkarabilirsiniz. Bir iş parçacığı meşgul bir MVar'a bir değer eklemeyi denediğinde, başka bir iş parçacığı değeri silene kadar iş parçacığı uyku moduna alınır. Boş bir MVar da benzer şekilde davranır: Boş bir MVar'dan bir şey çıkarmaya çalışırsanız, iş parçacığının başka bir iş parçacığının içine bir şey eklemesini beklemesi gerekir.

Haskell, eşzamanlılık için bir soyutlama katmanı sağlayan bir MVar geliştirmesi olan STM'ye giderek daha fazla güveniyor. İşlemler tek bir atomik işlem işlemi olarak gruplandırılabilir ve yürütülebilir. Önemli ölçüde daha az problemlidir ve kilitlenme veya yarış koşulları yoktur. STM altındaki değişkenler, Java'ya benzer şekilde STM'nin tüm bloğa uygulandığı TVar türündedir. MVar'ın STM'ye göre bir avantajı, iş parçacıklarının adil bir şekilde işlenmesidir. Bunu yapmak için MVar, ilk giren ilk çıkar prensibini uygular; bu, iş parçacıklarının MVar'a sırayla eriştiği anlamına gelir.

Haskell'de paralel programların çoğu deterministiktir, yani tekrarlanabilir sonuçlar üretirler. Bir fonksiyonu paralelleştirmek için yöntemler modülden gelir Control.Parallel kullanmak için. Bu, kilitlenmeler ve yarış koşulları gibi yaygın çoklu iş parçacığı sorunlarını önler.



Haberin Sonu
 
Üst