Python'da Eş Zamanlılık - Hızlı Kılavuz

Bu bölümde, Python'da eşzamanlılık kavramını anlayacak ve farklı iş parçacıkları ve süreçler hakkında bilgi edineceğiz.

Eşzamanlılık nedir?

Basit bir deyişle, eşzamanlılık, aynı anda iki veya daha fazla olayın meydana gelmesidir. Eşzamanlılık doğal bir fenomendir çünkü birçok olay herhangi bir zamanda aynı anda meydana gelir.

Programlama açısından, eşzamanlılık, iki görevin yürütülürken örtüştüğü zamandır. Eşzamanlı programlama ile uygulamalarımızın ve yazılım sistemlerimizin performansı iyileştirilebilir, çünkü bir öncekinin tamamlanmasını beklemek yerine isteklerle eşzamanlı olarak ilgilenebiliriz.

Eş Zamanlılığın Tarihsel İncelemesi

Aşağıdaki noktalar bize eşzamanlılığın kısa tarihsel incelemesini verecektir -

Demiryolları konseptinden

Eşzamanlılık, demiryolları kavramı ile yakından ilgilidir. Demiryolları ile, aynı demiryolu sistemi üzerinde birden çok trenin, her trenin hedefine güvenli bir şekilde varacağı şekilde idare edilmesi gerekiyordu.

Akademide eşzamanlı bilgi işlem

Bilgisayar bilimi eşzamanlılığına ilgi 1965 yılında Edsger W. Dijkstra tarafından yayınlanan araştırma makalesi ile başladı. Bu makalede, eşzamanlılık kontrolünün özelliği olan karşılıklı dışlama sorununu belirledi ve çözdü.

Üst düzey eşzamanlılık ilkelleri

Son zamanlarda, programcılar üst düzey eşzamanlılık ilkelerinin ortaya çıkması nedeniyle gelişmiş eşzamanlı çözümler alıyorlar.

Programlama dilleriyle geliştirilmiş eşzamanlılık

Google'ın Golang, Rust ve Python gibi programlama dilleri, daha iyi eşzamanlı çözümler elde etmemize yardımcı olan alanlarda inanılmaz gelişmeler kaydetti.

İş parçacığı ve çoklu okuma nedir?

Threadbir işletim sisteminde gerçekleştirilebilecek en küçük yürütme birimidir. Kendi başına bir program değildir, ancak bir program içinde çalışır. Başka bir deyişle, ipler birbirinden bağımsız değildir. Her iş parçacığı, kod bölümünü, veri bölümünü vb. Diğer iş parçacıklarıyla paylaşır. Hafif işlemler olarak da bilinirler.

Bir iş parçacığı aşağıdaki bileşenlerden oluşur -

  • Bir sonraki çalıştırılabilir talimatın adresinden oluşan program sayacı

  • Stack

  • Kayıt seti

  • Benzersiz bir kimlik

MultithreadingÖte yandan, bir CPU'nun aynı anda birden fazla iş parçacığı çalıştırarak işletim sistemi kullanımını yönetme yeteneğidir. Çoklu okumanın ana fikri, bir süreci birden çok iş parçacığına bölerek paralellik sağlamaktır. Çoklu okuma kavramı aşağıdaki örnek yardımıyla anlaşılabilir.

Misal

İçeriği yazmak için MS Word'ü açtığımız belirli bir işlemi yürüttüğümüzü varsayalım. MS Word'ü açmak için bir iş parçacığı atanacak ve içeriğin yazılması için başka bir iş parçacığı gerekli olacaktır. Ve şimdi, mevcut olanı düzenlemek istersek, düzenleme görevini yapmak için başka bir iş parçacığı gerekecektir.

Süreç ve çoklu işlem nedir?

Birprocesssistemde uygulanacak temel iş birimini temsil eden bir varlık olarak tanımlanır. Basit bir ifadeyle bilgisayar programlarımızı bir metin dosyasına yazıyoruz ve bu programı çalıştırdığımızda programda bahsedilen tüm görevleri yerine getiren bir işlem haline geliyor. Süreç yaşam döngüsü boyunca farklı aşamalardan geçer - Başlatma, Hazırlık, Çalıştırma, Bekleme ve Sonlandırma.

Aşağıdaki şema, bir sürecin farklı aşamalarını göstermektedir -

Bir işlem, birincil evre olarak adlandırılan tek bir iş parçacığına veya kendi kayıt kümelerine, program sayacına ve yığınına sahip birden çok evreye sahip olabilir. Aşağıdaki şema bize farkı gösterecektir -

Multiprocessing,Öte yandan, tek bir bilgisayar sistemi içinde iki veya daha fazla CPU biriminin kullanılmasıdır. Birincil hedefimiz, donanımımızdan tam potansiyeli elde etmektir. Bunu başarmak için bilgisayar sistemimizde bulunan tam sayıda CPU çekirdeğini kullanmamız gerekir. Çoklu işlem, bunu yapmak için en iyi yaklaşımdır.

Python, en popüler programlama dillerinden biridir. Eşzamanlı uygulamalar için uygun kılan nedenlerden bazıları şunlardır -

Sözdizimsel şeker

Sözdizimsel şeker, şeyleri okumayı veya ifade etmeyi kolaylaştırmak için tasarlanmış bir programlama dili içindeki sözdizimidir. Dili insan kullanımı için daha "tatlı" kılar: şeyler daha net, daha özlü veya tercihe göre alternatif bir tarzda ifade edilebilir. Python, nesneler üzerinde hareket etmek için tanımlanabilen Magic yöntemleriyle birlikte gelir. Bu Magic yöntemleri, sözdizimsel şeker olarak kullanılır ve daha kolay anlaşılır anahtar kelimelere bağlıdır.

Geniş Topluluk

Python dili, yapay zeka, makine öğrenimi, derin öğrenme ve nicel analiz alanında çalışan veri bilimcileri ve matematikçiler arasında büyük bir benimsenme oranına tanık oldu.

Eşzamanlı programlama için faydalı API'ler

Python 2 ve 3, paralel / eşzamanlı programlama için ayrılmış çok sayıda API'ye sahiptir. Bunların en popülerlerithreading, concurrent.features, multiprocessing, asyncio, gevent and greenlets, vb.

Eşzamanlı uygulamaların uygulanmasında Python'un sınırlamaları

Python, eşzamanlı uygulamalar için bir sınırlama ile birlikte gelir. Bu sınırlama denirGIL (Global Interpreter Lock)Python'da mevcuttur. GIL hiçbir zaman birden fazla CPU çekirdeği kullanmamıza izin vermez ve bu nedenle Python'da gerçek iş parçacığı olmadığını söyleyebiliriz. GIL kavramını şu şekilde anlayabiliriz -

GIL (Global Tercüman Kilidi)

Python dünyasındaki en tartışmalı konulardan biridir. CPython'da GIL mutekstir - işleri iş parçacığını güvenli kılan karşılıklı dışlama kilidi. Başka bir deyişle, GIL'in birden çok iş parçacığının Python kodunu paralel olarak çalıştırmasını engellediğini söyleyebiliriz. Kilit bir seferde yalnızca bir iş parçacığı tarafından tutulabilir ve bir iş parçacığı yürütmek istiyorsak önce kilidi almalıdır. Aşağıda gösterilen şema, GIL'in çalışmasını anlamanıza yardımcı olacaktır.

Ancak, Python'da aşağıdaki gibi bazı kitaplıklar ve uygulamalar vardır: Numpy, Jpython ve IronPytbhon. Bu kitaplıklar GIL ile herhangi bir etkileşim olmadan çalışır.

Çok iş parçacıklı programlarla ilişkili olarak hem eşzamanlılık hem de paralellik kullanılır, ancak aralarındaki benzerlik ve farklılık konusunda çok fazla kafa karışıklığı vardır. Bu konudaki büyük soru: eşzamanlılık paralelliği mi değil mi? Her iki terim de oldukça benzer görünse de, yukarıdaki sorunun cevabı HAYIR olsa da, eşzamanlılık ve paralellik aynı değildir. Şimdi, eğer aynı değillerse, aralarındaki temel fark nedir?

Basit bir ifadeyle, eşzamanlılık, farklı iş parçacıklarından paylaşılan duruma erişimin yönetilmesiyle ilgilenir ve diğer tarafta, paralellik, donanımın performansını artırmak için birden çok CPU'yu veya çekirdeğini kullanma ile ilgilidir.

Ayrıntılı Olarak Eş Zamanlılık

Eşzamanlılık, iki görevin yürütülürken çakışmasıdır. Bir uygulamanın aynı anda birden fazla göreve ilerlediği bir durum olabilir. Şematik olarak anlayabiliriz; aşağıdaki gibi birden çok görev aynı anda ilerliyor -

Eş Zamanlılık Seviyeleri

Bu bölümde, programlama açısından üç önemli eşzamanlılık düzeyini tartışacağız -

Düşük Seviye Eş Zamanlılık

Bu eşzamanlılık düzeyinde, atomik işlemlerin açık kullanımı vardır. Hataya çok açık olduğu ve hata ayıklaması zor olduğu için uygulama oluşturmak için bu tür bir eşzamanlılık kullanamayız. Python bile bu tür eşzamanlılığı desteklemez.

Orta Seviye Eş Zamanlılık

Bu eşzamanlılıkta, açık atomik işlemlerin kullanımı yoktur. Açık kilitleri kullanır. Python ve diğer programlama dilleri bu tür eşzamanlılığı destekler. Çoğunlukla uygulama programcıları bu eşzamanlılığı kullanır.

Üst Düzey Eş Zamanlılık

Bu eşzamanlılıkta, ne açık atomik işlemler ne de açık kilitler kullanılır. Python'daconcurrent.futures Bu tür eşzamanlılığı desteklemek için modül.

Eşzamanlı Sistemlerin Özellikleri

Bir programın veya eşzamanlı sistemin doğru olması için bazı özelliklerin onun tarafından karşılanması gerekir. Sistemin sonlandırılmasına ilişkin özellikler aşağıdaki gibidir -

Doğruluk özelliği

Doğruluk özelliği, programın veya sistemin istenen doğru yanıtı vermesi gerektiği anlamına gelir. Basit tutmak için, sistemin başlangıç ​​program durumunu son duruma doğru bir şekilde eşlemesi gerektiğini söyleyebiliriz.

Güvenlik özelliği

Güvenlik özelliği, programın veya sistemin bir “good” veya “safe” devlet ve asla hiçbir şey yapmaz “bad”.

Canlılık özelliği

Bu özellik, bir programın veya sistemin “make progress” ve arzu edilen bir duruma ulaşacaktır.

Eşzamanlı sistemlerin aktörleri

Bu, kendi görevlerinde ilerleme kaydetmek için aynı anda çalışan birden çok işlem ve iş parçacığının olabileceği eşzamanlı sistemin ortak bir özelliğidir. Bu süreçlere ve iş parçacıklarına eşzamanlı sistemin aktörleri denir.

Eşzamanlı Sistemlerin Kaynakları

Oyuncular görevlerini yerine getirebilmek için bellek, disk, yazıcı vb. Kaynakları kullanmalıdır.

Belirli kurallar seti

Her eşzamanlı sistem, aktörler tarafından gerçekleştirilecek görev türlerini ve her birinin zamanlamasını tanımlayan bir dizi kurala sahip olmalıdır. Görevler kilit alma, bellek paylaşımı, durumu değiştirme vb. Olabilir.

Eşzamanlı Sistemlerin Engelleri

Verilerin paylaşılması

Eşzamanlı sistemleri uygularken önemli bir konu, verilerin birden çok iş parçacığı veya süreç arasında paylaşılmasıdır. Gerçekte, programcı kilitlerin paylaşılan verileri koruduğundan emin olmalıdır, böylece ona tüm erişimler serileştirilir ve bir seferde yalnızca bir iş parçacığı veya işlem paylaşılan verilere erişebilir. Birden fazla iş parçacığı veya işlemin tümü aynı paylaşılan verilere erişmeye çalıştığında, hepsi değil, en az biri engellenecek ve boşta kalacaktır. Başka bir deyişle, kilit yürürlükte iken bir seferde sadece bir süreç veya iş parçacığı kullanabileceğimizi söyleyebiliriz. Yukarıda belirtilen engelleri kaldırmak için bazı basit çözümler olabilir -

Veri Paylaşımı Kısıtlaması

En basit çözüm, değiştirilebilir herhangi bir veriyi paylaşmamaktır. Bu durumda, açık kilitleme kullanmamıza gerek kalmaz ve karşılıklı verilerden kaynaklanan eşzamanlılık engeli çözülür.

Veri Yapısı Yardımı

Çoğu zaman eşzamanlı işlemlerin aynı verilere aynı anda erişmesi gerekir. Açık kilit kullanmaktan başka bir çözüm, eşzamanlı erişimi destekleyen bir veri yapısı kullanmaktır. Örneğin, kullanabilirizqueuemodül, iş parçacığı açısından güvenli kuyruklar sağlar. Ayrıca kullanabilirizmultiprocessing.JoinableQueue çoklu işlem tabanlı eşzamanlılık sınıfları.

Değişmez Veri Transferi

Bazen kullandığımız veri yapısı, mesela eşzamanlılık kuyruğu uygun değildir, o zaman değişmez verileri kilitlemeden geçirebiliriz.

Değişken Veri Aktarımı

Yukarıdaki çözümün devamında, eğer değişmez verilerden ziyade sadece değişken veriyi geçirmemiz gerekiyorsa, o zaman sadece okunabilen değişken veriyi geçirebileceğimizi varsayalım.

G / Ç Kaynaklarının Paylaşımı

Eşzamanlı sistemlerin uygulanmasında bir diğer önemli konu, iş parçacıkları veya süreçler tarafından G / Ç kaynaklarının kullanılmasıdır. Sorun, bir iş parçacığı veya işlem G / Ç'yi bu kadar uzun süre kullandığında ve diğeri boşta kaldığında ortaya çıkar. I / O ağır bir uygulama ile çalışırken bu tür bir engel görebiliriz. Web tarayıcısından sayfaların istenmesi bir örnek yardımı ile anlaşılabilir. Ağır bir uygulamadır. Burada, verinin talep edildiği hız, tüketildiği hızdan daha yavaşsa, eşzamanlı sistemimizde I / O bariyerimiz vardır.

Aşağıdaki Python betiği, bir web sayfası istemek ve ağımızın istenen sayfayı almak için harcadığı zamanı almak içindir -

import urllib.request
import time
ts = time.time()
req = urllib.request.urlopen('http://www.tutorialspoint.com')
pageHtml = req.read()
te = time.time()
print("Page Fetching Time : {} Seconds".format (te-ts))

Yukarıdaki komut dosyasını çalıştırdıktan sonra, sayfa getirme zamanını aşağıda gösterildiği gibi alabiliriz.

Çıktı

Page Fetching Time: 1.0991398811340332 Seconds

Sayfayı getirme süresinin bir saniyeden fazla olduğunu görebiliriz. Şimdi ya binlerce farklı web sayfasını getirmek istersek, ağımızın ne kadar zaman alacağını anlayabilirsiniz.

Paralellik nedir?

Paralellik, görevleri eşzamanlı olarak işlenebilen alt görevlere bölme sanatı olarak tanımlanabilir. Yukarıda tartışıldığı gibi, aynı anda iki veya daha fazla olayın meydana geldiği eşzamanlılığın tersidir. Şematik olarak anlayabiliriz; bir görev, aşağıdaki gibi paralel olarak işlenebilen birkaç alt göreve bölünür -

Eşzamanlılık ve paralellik arasındaki ayrım hakkında daha fazla fikir edinmek için aşağıdaki noktaları göz önünde bulundurun:

Eşzamanlı ama paralel değil

Bir uygulama eşzamanlı olabilir ancak paralel olmayabilir, aynı anda birden fazla görevi işlediği, ancak görevlerin alt görevlere ayrılmadığı anlamına gelir.

Paralel ama eşzamanlı değil

Bir uygulama paralel olabilir ancak eşzamanlı olmayabilir, bir seferde yalnızca bir görev üzerinde çalıştığı ve alt görevlere bölünmüş görevlerin paralel olarak işlenebileceği anlamına gelir.

Ne paralel ne de eşzamanlı

Bir uygulama ne paralel ne de eşzamanlı olabilir. Bu, aynı anda yalnızca bir görev üzerinde çalıştığı ve görevin asla alt görevlere ayrılmadığı anlamına gelir.

Hem paralel hem de eşzamanlı

Bir uygulama hem paralel hem de eşzamanlı olabilir, yani aynı anda birden çok görevde çalışır ve görev, bunları paralel olarak yürütmek için alt görevlere bölünür.

Paralelliğin Gerekliliği

Alt görevleri tek CPU'nun farklı çekirdekleri arasında veya bir ağa bağlı birden fazla bilgisayar arasında dağıtarak paralellik sağlayabiliriz.

Paralelliğe ulaşmak için neden gerekli olduğunu anlamak için aşağıdaki önemli noktaları göz önünde bulundurun -

Verimli kod yürütme

Paralellik yardımıyla kodumuzu verimli bir şekilde çalıştırabiliriz. Aynı kod parçalar halinde paralel olarak çalıştığı için zamandan tasarruf edecek.

Sıralı hesaplamadan daha hızlı

Sıralı hesaplama, daha hızlı hesaplama sonuçları elde etmenin mümkün olmadığı fiziksel ve pratik faktörlerle sınırlıdır. Öte yandan, bu sorun paralel hesaplamayla çözülür ve bize sıralı hesaplamadan daha hızlı hesaplama sonuçları verir.

Daha az uygulama süresi

Paralel işleme, program kodunun yürütme süresini azaltır.

Gerçek hayattaki paralellik örneğinden bahsedecek olursak, bilgisayarımızın grafik kartı paralel işlemenin gerçek gücünü vurgulayan örnektir çünkü bağımsız olarak çalışan ve aynı anda yürütmeyi yapabilen yüzlerce ayrı işlemci çekirdeğine sahiptir. Bu nedenle, üst düzey uygulamaları ve oyunları da çalıştırabiliyoruz.

Uygulama için işlemcilerin anlaşılması

Eşzamanlılık, paralellik ve aralarındaki farkı biliyoruz ama uygulanacağı sistem ne olacak? Uygulayacağımız sistem anlayışına sahip olmak çok gerekli çünkü yazılımı tasarlarken bilinçli kararlar almamız bize fayda sağlıyor. Aşağıdaki iki tür işlemciye sahibiz -

Tek çekirdekli işlemciler

Tek çekirdekli işlemciler, herhangi bir zamanda bir iş parçacığını yürütebilir. Bu işlemciler kullanırcontext switchingbir iş parçacığı için gerekli tüm bilgileri belirli bir zamanda depolamak ve ardından bilgileri daha sonra geri yüklemek için. Bağlam değiştirme mekanizması, belirli bir saniye içinde bir dizi iş parçacığı üzerinde ilerleme kaydetmemize yardımcı olur ve sistem birden çok şey üzerinde çalışıyormuş gibi görünür.

Tek çekirdekli işlemcilerin birçok avantajı vardır. Bu işlemciler daha az güç gerektirir ve birden çok çekirdek arasında karmaşık bir iletişim protokolü yoktur. Öte yandan, tek çekirdekli işlemcilerin hızı sınırlıdır ve daha büyük uygulamalar için uygun değildir.

Çok çekirdekli işlemciler

Çok çekirdekli işlemcilerin birden çok bağımsız işlem birimi vardır. cores.

Bu tür işlemciler bağlam değiştirme mekanizmasına ihtiyaç duymaz, çünkü her çekirdek bir dizi depolanan talimatı yürütmek için ihtiyaç duyduğu her şeyi içerir.

Getirme-Kod Çözme-Yürütme Döngüsü

Çok çekirdekli işlemcilerin çekirdekleri, yürütme için bir döngü izler. Bu döngü denirFetch-Decode-Executedöngü. Aşağıdaki adımları içerir -

Getir

Bu, program belleğinden talimatların alınmasını içeren çevrimin ilk adımıdır.

Kod çözme

Yakın zamanda alınan talimatlar, CPU'nun diğer bölümlerini tetikleyecek bir dizi sinyale dönüştürülecektir.

Yürüt

Alınan ve kodu çözülen talimatların yürütüleceği son adımdır. Yürütmenin sonucu bir CPU kaydında saklanacaktır.

Buradaki avantajlardan biri, çok çekirdekli işlemcilerdeki yürütmenin tek çekirdekli işlemcilerden daha hızlı olmasıdır. Daha büyük uygulamalar için uygundur. Öte yandan, birden çok çekirdek arasındaki karmaşık iletişim protokolü bir sorundur. Birden çok çekirdek, tek çekirdekli işlemcilerden daha fazla güç gerektirir.

Programı veya eşzamanlı sistemi tasarlarken dikkate alınması gereken farklı sistem ve bellek mimarisi stilleri vardır. Bu çok gereklidir, çünkü bir sistem ve bellek stili bir görev için uygun olabilir, ancak diğer göreve hataya açık olabilir.

Eşzamanlılığı destekleyen bilgisayar sistemi mimarileri

Michael Flynn, 1972'de bilgisayar sistemi mimarisinin farklı stillerini kategorize etmek için taksonomi verdi. Bu sınıflandırma, dört farklı stili şu şekilde tanımlar:

  • Tek talimat akışı, tek veri akışı (SISD)
  • Tek talimat akışı, çoklu veri akışı (SIMD)
  • Çoklu talimat akışı, tek veri akışı (MISD)
  • Çoklu talimat akışı, çoklu veri akışı (MIMD).

Tek talimat akışı, tek veri akışı (SISD)

Adından da anlaşılacağı gibi, bu tür sistemler, bir sıralı gelen veri akışına ve veri akışını yürütmek için tek bir işlem birimine sahip olacaktır. Paralel bilgi işlem mimarisine sahip tek işlemcili sistemler gibidirler. SISD'nin mimarisi aşağıdadır -

SISD'nin Avantajları

SISD mimarisinin avantajları aşağıdaki gibidir -

  • Daha az güç gerektirir.
  • Birden çok çekirdek arasında karmaşık iletişim protokolü sorunu yoktur.

SISD'nin dezavantajları

SISD mimarisinin dezavantajları aşağıdaki gibidir -

  • SISD mimarisinin hızı, tıpkı tek çekirdekli işlemciler gibi sınırlıdır.
  • Daha büyük uygulamalar için uygun değildir.

Tek talimat akışı, çoklu veri akışı (SIMD)

Adından da anlaşılacağı gibi, bu tür sistemler birden çok gelen veri akışına ve herhangi bir zamanda tek bir talimat üzerinde hareket edebilen işlem birimlerinin sayısına sahip olacaktır. Paralel bilgi işlem mimarisine sahip çok işlemcili sistemler gibidirler. SIMD'nin mimarisi aşağıdadır -

SIMD için en iyi örnek grafik kartlarıdır. Bu kartların yüzlerce ayrı işlem birimi vardır. SISD ve SIMD arasındaki hesaplama farkından bahsedersek, dizileri eklemek için[5, 15, 20] ve [15, 25, 10],SISD mimarisinin üç farklı ekleme işlemi gerçekleştirmesi gerekir. Öte yandan, SIMD mimarisi ile tek bir ekleme işlemi ekleyebiliriz.

SIMD'nin Avantajları

SIMD mimarisinin avantajları aşağıdaki gibidir -

  • Birden fazla eleman üzerinde aynı işlem yalnızca bir komut kullanılarak gerçekleştirilebilir.

  • İşlemcinin çekirdek sayısı artırılarak sistemin verimi artırılabilir.

  • İşlem hızı SISD mimarisinden daha yüksektir.

SIMD'nin dezavantajları

SIMD mimarisinin dezavantajları aşağıdaki gibidir -

  • İşlemci çekirdeği sayısı arasında karmaşık bir iletişim vardır.
  • Maliyet SISD mimarisinden daha yüksektir.

Çoklu Talimat Tek Veri (MISD) akışı

MISD akışına sahip sistemler, aynı veri seti üzerinde farklı talimatlar uygulayarak farklı işlemler gerçekleştiren çok sayıda işleme birimine sahiptir. MISD'nin mimarisi aşağıdadır -

MISD mimarisinin temsilcileri henüz ticari olarak mevcut değil.

Çoklu Talimat Çoklu Veri (MIMD) akışı

MIMD mimarisini kullanan sistemde, çok işlemcili bir sistemdeki her işlemci, paralel olarak farklı veri kümeleri üzerinde bağımsız olarak farklı talimat kümelerini yürütebilir. Tek işlemin birden çok veri seti üzerinde yürütüldüğü SIMD mimarisinin tam tersidir. MIMD'nin mimarisi aşağıdadır -

Normal bir çoklu işlemci MIMD mimarisini kullanır. Bu mimariler temel olarak bilgisayar destekli tasarım / bilgisayar destekli üretim, simülasyon, modelleme, iletişim anahtarları vb. Gibi bir dizi uygulama alanında kullanılmaktadır.

Eşzamanlılığı destekleyen bellek mimarileri

Eşzamanlılık ve paralellik gibi kavramlarla çalışırken her zaman programları hızlandırmaya ihtiyaç vardır. Bilgisayar tasarımcıları tarafından bulunan bir çözüm, paylaşımlı bellekli çoklu bilgisayarlar, yani bir işlemcinin sahip olduğu tüm çekirdekler tarafından erişilen tek fiziksel adres alanına sahip bilgisayarlar yaratmaktır. Bu senaryoda, birkaç farklı mimari tarzı olabilir, ancak aşağıdakiler üç önemli mimari tarzıdır:

UMA (Tekdüzen Bellek Erişimi)

Bu modelde, tüm işlemciler fiziksel belleği aynı şekilde paylaşır. Tüm işlemcilerin tüm bellek sözcüklerine eşit erişim süresi vardır. Her işlemcinin özel bir önbelleği olabilir. Çevresel cihazlar bir dizi kurala uyar.

Tüm işlemciler tüm çevresel aygıtlara eşit erişime sahip olduğunda, sisteme symmetric multiprocessor. Çevre aygıtlarına yalnızca bir veya birkaç işlemci erişebildiğinde, sistemeasymmetric multiprocessor.

Tekdüzen Olmayan Bellek Erişimi (NUMA)

NUMA çok işlemcili modelde, erişim süresi bellek sözcüğünün konumuna göre değişir. Burada paylaşılan hafıza, yerel hafıza adı verilen tüm işlemciler arasında fiziksel olarak dağıtılır. Tüm yerel belleklerin toplanması, tüm işlemciler tarafından erişilebilen küresel bir adres alanı oluşturur.

Yalnızca Önbellek Bellek Mimarisi (COMA)

COMA modeli, NUMA modelinin özel bir versiyonudur. Burada, dağıtılan tüm ana bellekler önbellek belleğine dönüştürülür.

Genel olarak, bildiğimiz gibi, iplik genellikle pamuklu veya ipek kumaştan çok ince bükümlü bir iptir ve giysi dikmek için kullanılır. Aynı terim, bilgisayar programlama dünyasında da kullanılmaktadır. Şimdi, giysi dikmek için kullanılan ipliği bilgisayar programlaması için kullanılan iplikle nasıl ilişkilendireceğiz? İki iş parçacığı tarafından gerçekleştirilen roller burada benzerdir. Giysilerde iplik, bezi bir arada tutar ve diğer tarafta, bilgisayar programlamasında, iplik bilgisayar programını tutar ve programın aynı anda sıralı eylemleri veya birçok eylemi gerçekleştirmesine izin verir.

Threadbir işletim sistemindeki en küçük yürütme birimidir. Kendi başına bir program değildir, ancak bir program içinde çalışır. Başka bir deyişle, evreler birbirinden bağımsız değildir ve kod bölümünü, veri bölümünü vb. Diğer evreler ile paylaşır. Bu iplikler aynı zamanda hafif işlemler olarak da bilinir.

İplik Durumları

İş parçacığının işlevselliğini derinlemesine anlamak için, iş parçacığının yaşam döngüsü veya farklı iş parçacığı durumları hakkında bilgi edinmemiz gerekir. Tipik olarak, bir iş parçacığı beş farklı durumda bulunabilir. Farklı durumlar aşağıda gösterilmiştir -

Yeni Konu

Yeni bir iş parçacığı, yeni durumda yaşam döngüsüne başlar. Ancak bu aşamada henüz başlamamış ve herhangi bir kaynak tahsis edilmemiştir. Bunun sadece bir nesnenin örneği olduğunu söyleyebiliriz.

Runnable

Yeni doğan iplik başlatıldığında, iplik çalıştırılabilir hale gelir, yani çalışmayı bekler. Bu durumda, tüm kaynaklara sahiptir, ancak yine de görev zamanlayıcı çalışacak şekilde zamanlamamıştır.

Koşu

Bu durumda, iş parçacığı ilerleme kaydeder ve çalıştırmak için görev zamanlayıcı tarafından seçilen görevi yürütür. Şimdi, iş parçacığı ya ölü duruma ya da çalıştırılamaz / bekleme durumuna gidebilir.

Çalışmıyor / beklemede

Bu durumda, iş parçacığı duraklatılır çünkü ya bazı G / Ç isteğinin yanıtını beklemektedir ya da diğer iş parçacığının yürütülmesinin tamamlanmasını beklemektedir.

Ölü

Çalıştırılabilir bir evre, görevini tamamladığında veya başka şekilde sona erdiğinde sonlandırılmış duruma girer.

Aşağıdaki şema, bir iş parçacığının tüm yaşam döngüsünü göstermektedir -

İplik Türleri

Bu bölümde, farklı iplik türlerini göreceğiz. Türler aşağıda açıklanmıştır -

Kullanıcı Seviyesi Konuları

Bunlar kullanıcı tarafından yönetilen konulardır.

Bu durumda, iş parçacığı yönetimi çekirdeği evrelerin varlığından haberdar değildir. İş parçacığı kitaplığı evrelerin yaratılması ve yok edilmesi, ileti ve verilerin evreler arasında iletilmesi, iş parçacığı yürütmesinin programlanması ve iş parçacığı bağlamlarının kaydedilmesi ve geri yüklenmesi için kod içerir. Uygulama tek bir iş parçacığı ile başlar.

Kullanıcı düzeyindeki iş parçacığı örnekleri şunlardır:

  • Java konuları
  • POSIX konuları

Kullanıcı seviyesindeki Konuların Avantajları

Aşağıdakiler, kullanıcı seviyesi konularının farklı avantajlarıdır -

  • İş parçacığı değiştirme, Kernel modu ayrıcalıkları gerektirmez.
  • Kullanıcı seviyesi iş parçacığı herhangi bir işletim sisteminde çalışabilir.
  • Çizelgeleme, kullanıcı seviyesi iş parçacığında uygulamaya özel olabilir.
  • Kullanıcı düzeyinde iş parçacıkları oluşturmak ve yönetmek hızlıdır.

Kullanıcı seviyesindeki Konuların dezavantajları

Aşağıdakiler, kullanıcı seviyesi konularının farklı dezavantajlarıdır -

  • Tipik bir işletim sisteminde, çoğu sistem çağrısı engelliyor.
  • Çok iş parçacıklı uygulama, çoklu işlemden yararlanamaz.

Çekirdek Seviyesi Konuları

İşletim Sistemi tarafından yönetilen iş parçacıkları, bir işletim sistemi çekirdeği olan çekirdek üzerinde hareket eder.

Bu durumda Kernel, iş parçacığı yönetimi yapar. Uygulama alanında iş parçacığı yönetimi kodu yoktur. Çekirdek iş parçacıkları doğrudan işletim sistemi tarafından desteklenir. Herhangi bir uygulama, çok iş parçacıklı olacak şekilde programlanabilir. Bir uygulama içindeki tüm iş parçacıkları tek bir süreç içinde desteklenir.

Çekirdek, bir bütün olarak sürecin bağlam bilgisini ve süreç içindeki tek tek iş parçacıklarını saklar. Çekirdek tarafından zamanlama iş parçacığı temelinde yapılır. Kernel, Kernel alanında iş parçacığı oluşturma, planlama ve yönetim gerçekleştirir. Çekirdek iş parçacıklarının oluşturulması ve yönetilmesi genellikle kullanıcı iş parçacıklarından daha yavaştır. Çekirdek seviyesi iş parçacığı örnekleri Windows, Solaris'tir.

Çekirdek Seviyesi Konularının Avantajları

Aşağıda, çekirdek düzeyinde iş parçacıklarının farklı avantajları verilmiştir -

  • Çekirdek aynı anda birden çok işlemde aynı işlemden birden çok iş parçacığı planlayabilir.

  • İşlemdeki bir iş parçacığı engellenirse, Çekirdek aynı işlemin başka bir iş parçacığını zamanlayabilir.

  • Çekirdek rutinleri çok iş parçacıklı olabilir.

Çekirdek Seviyesi Konularının Dezavantajları

  • Çekirdek iş parçacıklarının oluşturulması ve yönetilmesi genellikle kullanıcı iş parçacıklarından daha yavaştır.

  • Aynı süreç içinde bir iş parçacığından diğerine kontrol aktarımı, Çekirdeğe bir mod anahtarı gerektirir.

İplik Kontrol Bloğu - TCB

İş Parçacığı Kontrol Bloğu (TCB), temel olarak iş parçacığı hakkında bilgi içeren işletim sistemi çekirdeğindeki veri yapısı olarak tanımlanabilir. TCB'de depolanan iş parçacığına özgü bilgiler, her işlemle ilgili bazı önemli bilgileri vurgulayacaktır.

TCB'de bulunan konularla ilgili aşağıdaki noktaları göz önünde bulundurun -

  • Thread identification - Her yeni iş parçacığına atanan benzersiz iş parçacığı kimliğidir (tid).

  • Thread state - İş parçacığının durumu (Çalışıyor, Çalıştırılabilir, Çalışmıyor, Ölü) ile ilgili bilgileri içerir.

  • Program Counter (PC) - İş parçacığının mevcut program talimatını gösterir.

  • Register set - İş parçacığının hesaplamalar için kendilerine atanan yazmaç değerlerini içerir.

  • Stack Pointer- İşlemdeki iş parçacığının yığınına işaret eder. İş parçacığının kapsamı altındaki yerel değişkenleri içerir.

  • Pointer to PCB - Bu iş parçacığını oluşturan işlemin işaretçisini içerir.

Süreç ve iş parçacığı arasındaki ilişki

Çoklu kullanımda, süreç ve iş parçacığı, bilgisayarın aynı anda birden fazla şeyi yapabilmesini sağlamak için aynı amaca sahip, birbiriyle çok yakından ilişkili iki terimdir. Bir süreç bir veya daha fazla iş parçacığı içerebilir, ancak tersine iş parçacığı bir işlem içeremez. Ancak, ikisi de iki temel yürütme birimi olmaya devam ediyor. Bir dizi talimatı yürüten bir program, hem süreci hem de iş parçacığını başlatır.

Aşağıdaki tablo süreç ve iş parçacığı arasındaki karşılaştırmayı gösterir -

İşlem Konu
Süreç ağırdır veya kaynak yoğundur. İş parçacığı hafiftir ve bir işlemden daha az kaynak gerektirir.
Süreç değiştirme, işletim sistemiyle etkileşim gerektirir. İş parçacığı değiştirmenin işletim sistemiyle etkileşime girmesine gerek yoktur.
Birden çok işlem ortamında, her işlem aynı kodu yürütür ancak kendi belleğine ve dosya kaynaklarına sahiptir. Tüm iş parçacıkları aynı açık dosya kümesini, alt işlemleri paylaşabilir.
Bir işlem engellenirse, ilk işlem engeli kaldırılıncaya kadar başka hiçbir işlem yürütülemez. Bir iş parçacığı engellenip beklerken, aynı görevdeki ikinci bir iş parçacığı çalıştırılabilir.
İş parçacığı kullanmadan birden çok işlem daha fazla kaynak kullanır. Çoklu iş parçacıklı süreçler daha az kaynak kullanır.
Çoklu süreçlerde her süreç diğerlerinden bağımsız olarak çalışır. Bir iş parçacığı başka bir iş parçacığının verilerini okuyabilir, yazabilir veya değiştirebilir.
Üst süreçte herhangi bir değişiklik olursa, alt süreçleri etkilemez. Ana iş parçacığında herhangi bir değişiklik olursa, bu işlemin diğer iş parçacıklarının davranışını etkileyebilir.
Kardeş süreçlerle iletişim kurmak için süreçler süreçler arası iletişimi kullanmalıdır. Konular, bu sürecin diğer konuları ile doğrudan iletişim kurabilir.

Multithreading Kavramı

Daha önce tartıştığımız gibi, Multithreading, bir CPU'nun aynı anda birden fazla iş parçacığı çalıştırarak işletim sistemi kullanımını yönetme becerisidir. Çoklu okumanın ana fikri, bir süreci birden çok iş parçacığına bölerek paralellik sağlamaktır. Daha basit bir şekilde, çoklu iş parçacığının, iş parçacığı kavramını kullanarak çoklu görevi başarmanın yolu olduğunu söyleyebiliriz.

Çoklu okuma kavramı aşağıdaki örnek yardımıyla anlaşılabilir.

Misal

Bir işlem yürüttüğümüzü varsayalım. Süreç, bir şeyler yazmak için MS kelimesini açmak olabilir. Bu tür bir işlemde, bir iş parçacığı MS sözcüğünü açmak için atanacak ve yazmak için başka bir iş parçacığı gerekecektir. Şimdi, bir şeyi düzenlemek istiyorsak, düzenleme görevini yapmak için başka bir iş parçacığı gerekeceğini varsayalım.

Aşağıdaki şema, bellekte nasıl birden fazla iş parçacığı bulunduğunu anlamamıza yardımcı olur -

Yukarıdaki diyagramda, her iş parçacığının kendi kayıt kümesini ve yerel değişkenleri içerdiği bir süreç içinde birden fazla evrenin var olabileceğini görebiliriz. Bunun dışında, bir süreçteki tüm iş parçacıkları genel değişkenleri paylaşır.

Multithreading Artıları

Şimdi çoklu kullanımın birkaç avantajını görelim. Avantajlar aşağıdaki gibidir -

  • Speed of communication - Çok iş parçacıklı okuma, hesaplama hızını artırır, çünkü her bir çekirdek veya işlemci aynı anda ayrı iş parçacıkları işler.

  • Program remains responsive - Bir iş parçacığı girdiyi beklerken diğeri aynı anda bir GUI çalıştırdığı için bir programın yanıt vermesini sağlar.

  • Access to global variables - Çoklu kullanımda, belirli bir sürecin tüm iş parçacıkları genel değişkenlere erişebilir ve genel değişkende herhangi bir değişiklik varsa, o zaman diğer iş parçacıkları tarafından da görülebilir.

  • Utilization of resources - Her programda birkaç iş parçacığının çalıştırılması, CPU'nun daha iyi kullanılmasını sağlar ve CPU'nun boşta kalma süresi azalır.

  • Sharing of data - Her iş parçacığı için fazladan alana gerek yoktur, çünkü bir programdaki evreler aynı verileri paylaşabilir.

Multithreading Eksileri

Şimdi çoklu kullanımın birkaç dezavantajını görelim. Dezavantajlar aşağıdaki gibidir -

  • Not suitable for single processor system - Multithreading, çok işlemcili sistemdeki performansa kıyasla tek işlemcili sistemde hesaplama hızı açısından performans elde etmeyi zor buluyor.

  • Issue of security - Bir programdaki tüm iş parçacığının aynı verileri paylaştığını bildiğimiz için, bu nedenle her zaman bir güvenlik sorunu vardır, çünkü bilinmeyen herhangi bir iş parçacığı verileri değiştirebilir.

  • Increase in complexity - Çoklu okuma, programın karmaşıklığını artırabilir ve hata ayıklama zorlaşır.

  • Lead to deadlock state - Çoklu okuma, programı kilitlenme durumuna ulaşma olasılığına yol açabilir.

  • Synchronization required- Karşılıklı dışlamayı önlemek için senkronizasyon gereklidir. Bu, daha fazla bellek ve CPU kullanımına yol açar.

Bu bölümde, iş parçacıklarının Python'da nasıl uygulanacağını öğreneceğiz.

Thread Uygulaması için Python Modülü

Python iş parçacıkları bazen hafif işlemler olarak adlandırılır çünkü iş parçacıkları işlemlerden çok daha az bellek kullanır. İş parçacığı, aynı anda birden fazla görevi gerçekleştirmeye izin verir. Python'da, bir programda iş parçacıkları uygulayan aşağıdaki iki modüle sahibiz:

  • <_thread>module

  • <threading>module

Bu iki modül arasındaki temel fark şudur: <_thread> modül bir iş parçacığını bir işlev olarak ele alırken, <threading>modül her evreyi bir nesne olarak ele alır ve nesneye yönelik bir şekilde uygular. Dahası,<_thread>modülü, düşük seviyeli diş açmada etkilidir ve <threading> modül.

<_thread> modülü

Python'un önceki sürümünde, <thread>modül, ancak oldukça uzun bir süredir "kullanımdan kaldırıldı" olarak kabul edildi. Kullanıcılar şunları kullanmaya teşvik edilmiştir:<threading>modül yerine. Bu nedenle, Python 3'te "thread" modülü artık mevcut değildir. "Olarak yeniden adlandırıldı<_thread>"Python3'teki geriye dönük uyumsuzluklar için.

Yardımıyla yeni iş parçacığı oluşturmak <_thread> modülü aramalıyız start_new_threadyöntemi. Bu yöntemin çalışması, aşağıdaki sözdizimi yardımıyla anlaşılabilir -

_thread.start_new_thread ( function, args[, kwargs] )

Burada -

  • args bir grup argümandır

  • kwargs isteğe bağlı bir anahtar kelime argümanları sözlüğüdür

Bir argüman iletmeden işlevi çağırmak istiyorsak, o zaman içinde boş bir argüman demeti kullanmamız gerekir. args.

Bu yöntem çağrısı hemen döner, çocuk evre başlar ve işlevi, varsa, args'ın geçirilen listesiyle çağırır. İş parçacığı, işlev döndüğünde sona erer.

Misal

Aşağıda, kullanarak yeni iş parçacığı oluşturmaya bir örnek verilmiştir. <_thread>modül. Burada start_new_thread () yöntemini kullanıyoruz.

import _thread
import time

def print_time( threadName, delay):
   count = 0
   while count < 5:
      time.sleep(delay)
      count += 1
      print ("%s: %s" % ( threadName, time.ctime(time.time()) ))

try:
   _thread.start_new_thread( print_time, ("Thread-1", 2, ) )
   _thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
   print ("Error: unable to start thread")
while 1:
   pass

Çıktı

Aşağıdaki çıktı, yeni iş parçacıklarının neslini anlamamıza yardımcı olacaktır. <_thread> modül.

Thread-1: Mon Apr 23 10:03:33 2018
Thread-2: Mon Apr 23 10:03:35 2018
Thread-1: Mon Apr 23 10:03:35 2018
Thread-1: Mon Apr 23 10:03:37 2018
Thread-2: Mon Apr 23 10:03:39 2018
Thread-1: Mon Apr 23 10:03:39 2018
Thread-1: Mon Apr 23 10:03:41 2018
Thread-2: Mon Apr 23 10:03:43 2018
Thread-2: Mon Apr 23 10:03:47 2018
Thread-2: Mon Apr 23 10:03:51 2018

<iş parçacığı> modülü

<threading>modülü nesneye yönelik bir şekilde uygular ve her evreyi bir nesne olarak ele alır. Bu nedenle, iş parçacıkları için <_thread> modülünden çok daha güçlü, üst düzey destek sağlar. Bu modül Python 2.4'e dahildir.

<threading> modülündeki ek yöntemler

<threading> modülün tüm yöntemlerini içerir <_thread>modül ancak ek yöntemler de sağlar. Ek yöntemler aşağıdaki gibidir -

  • threading.activeCount() - Bu yöntem, aktif olan iş parçacığı nesnelerinin sayısını döndürür

  • threading.currentThread() - Bu yöntem, çağıranın iş parçacığı kontrolündeki iş parçacığı nesnelerinin sayısını döndürür.

  • threading.enumerate() - Bu yöntem, şu anda aktif olan tüm iş parçacığı nesnelerinin bir listesini döndürür.

  • Diş çekme uygulamak için, <threading> modülde Thread Aşağıdaki yöntemleri sağlayan sınıf -

    • run() - run () yöntemi, bir iş parçacığının giriş noktasıdır.

    • start() - start () yöntemi, çalıştırma yöntemini çağırarak bir iş parçacığı başlatır.

    • join([time]) - join (), iş parçacıklarının sona ermesini bekler.

    • isAlive() - isAlive () yöntemi, bir iş parçacığının hala yürütülmekte olup olmadığını kontrol eder.

    • getName() - getName () yöntemi bir iş parçacığının adını döndürür.

    • setName() - setName () yöntemi bir iş parçacığının adını belirler.

<Threading> modülünü kullanarak iş parçacığı nasıl oluşturulur?

Bu bölümde, kullanarak iş parçacığı oluşturmayı öğreneceğiz. <threading>modül. <Threading> modülünü kullanarak yeni bir iş parçacığı oluşturmak için şu adımları izleyin -

  • Step 1 - Bu adımda, yeni bir alt sınıf tanımlamamız gerekiyor. Thread sınıf.

  • Step 2 - Daha sonra ek argümanlar eklemek için, __init__(self [,args]) yöntem.

  • Step 3 - Bu adımda, başlatıldığında iş parçacığının ne yapması gerektiğini uygulamak için run (self [, args]) yöntemini geçersiz kılmamız gerekir.

  • Şimdi, yenisini oluşturduktan sonra Thread alt sınıf, bunun bir örneğini oluşturabilir ve ardından yeni bir iş parçacığı başlatabiliriz. start()bu da sırayla run() yöntem.

Misal

Nasıl yeni bir iş parçacığı oluşturacağınızı öğrenmek için bu örneği düşünün. <threading> modül.

import threading
import time
exitFlag = 0

class myThread (threading.Thread):
   def __init__(self, threadID, name, counter):
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.counter = counter
   def run(self):
      print ("Starting " + self.name)
      print_time(self.name, self.counter, 5)
      print ("Exiting " + self.name)
def print_time(threadName, delay, counter):
   while counter:
      if exitFlag:
         threadName.exit()
      time.sleep(delay)
      print ("%s: %s" % (threadName, time.ctime(time.time())))
      counter -= 1

thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("Exiting Main Thread")
Starting Thread-1
Starting Thread-2

Çıktı

Şimdi, aşağıdaki çıktıyı düşünün -

Thread-1: Mon Apr 23 10:52:09 2018
Thread-1: Mon Apr 23 10:52:10 2018
Thread-2: Mon Apr 23 10:52:10 2018
Thread-1: Mon Apr 23 10:52:11 2018
Thread-1: Mon Apr 23 10:52:12 2018
Thread-2: Mon Apr 23 10:52:12 2018
Thread-1: Mon Apr 23 10:52:13 2018
Exiting Thread-1
Thread-2: Mon Apr 23 10:52:14 2018
Thread-2: Mon Apr 23 10:52:16 2018
Thread-2: Mon Apr 23 10:52:18 2018
Exiting Thread-2
Exiting Main Thread

Çeşitli Diş Durumları için Python Programı

Beş iş parçacığı durumu vardır - yeni, çalıştırılabilir, koşuyor, bekliyor ve ölü. Bu beşinden, esas olarak üç eyalete odaklanacağız - koşmak, beklemek ve ölmek. Bir iş parçacığı kaynaklarını çalışma durumunda alır, bekleme durumunda kaynakları bekler; Yürütülüyorsa ve ediniliyorsa kaynağın son serbest bırakılması ölü durumdaysa.

Start (), sleep () ve join () yöntemlerinin yardımıyla aşağıdaki Python programı, bir iş parçacığının sırasıyla çalışan, bekleme ve ölü duruma nasıl girildiğini gösterecektir.

Step 1 - Gerekli modülleri, <iş parçacığı> ve <zaman> içe aktarın

import threading
import time

Step 2 - Bir iş parçacığı oluştururken çağrılacak bir işlev tanımlayın.

def thread_states():
   print("Thread entered in running state")

Step 3 - İş parçacığımızı 2 saniye bekletmek için zaman modülünün sleep () yöntemini kullanıyoruz.

time.sleep(2)

Step 4 - Şimdi, yukarıda tanımlanan fonksiyonun argümanını alan T1 adında bir evre oluşturuyoruz.

T1 = threading.Thread(target=thread_states)

Step 5- Şimdi, start () işlevi yardımıyla iş parçacığımızı başlatabiliriz. Fonksiyonu tanımlarken tarafımızdan belirlenen mesajı üretecektir.

T1.start()
Thread entered in running state

Step 6 - Artık, iş parçacığını çalıştırmayı bitirdikten sonra join () yöntemi ile öldürebiliriz.

T1.join()

Python'da iş parçacığı başlatmak

Python'da farklı yollarla yeni bir iş parçacığı başlatabiliriz ancak bunların en kolayı onu tek bir işlev olarak tanımlamaktır. Fonksiyonu tanımladıktan sonra, bunu yeni bir hedef olarak geçebiliriz.threading.Threadnesne vb. İşlevin nasıl çalıştığını anlamak için aşağıdaki Python kodunu yürütün -

import threading
import time
import random
def Thread_execution(i):
   print("Execution of Thread {} started\n".format(i))
   sleepTime = random.randint(1,4)
   time.sleep(sleepTime)
   print("Execution of Thread {} finished".format(i))
for i in range(4):
   thread = threading.Thread(target=Thread_execution, args=(i,))
   thread.start()
   print("Active Threads:" , threading.enumerate())

Çıktı

Execution of Thread 0 started
Active Threads:
   [<_MainThread(MainThread, started 6040)>,
      <HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
      <Thread(Thread-3576, started 3932)>]

Execution of Thread 1 started
Active Threads:
   [<_MainThread(MainThread, started 6040)>,
      <HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
      <Thread(Thread-3576, started 3932)>,
      <Thread(Thread-3577, started 3080)>]

Execution of Thread 2 started
Active Threads:
   [<_MainThread(MainThread, started 6040)>,
      <HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
      <Thread(Thread-3576, started 3932)>,
      <Thread(Thread-3577, started 3080)>,
      <Thread(Thread-3578, started 2268)>]

Execution of Thread 3 started
Active Threads:
   [<_MainThread(MainThread, started 6040)>,
      <HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
      <Thread(Thread-3576, started 3932)>,
      <Thread(Thread-3577, started 3080)>,
      <Thread(Thread-3578, started 2268)>,
      <Thread(Thread-3579, started 4520)>]
Execution of Thread 0 finished
Execution of Thread 1 finished
Execution of Thread 2 finished
Execution of Thread 3 finished

Python'da Daemon konuları

Python'da daemon evrelerini uygulamadan önce, artalan süreçleri ve bunların kullanımı hakkında bilgi sahibi olmamız gerekir. Hesaplama açısından daemon, veri gönderme, dosya transferleri vb. Gibi çeşitli hizmetlere yönelik istekleri işleyen bir arka plan işlemidir. Artık gerekli değilse, uykuda olacaktır. Aynı görev, arka plan programı olmayan iş parçacıkları yardımıyla da yapılabilir. Ancak, bu durumda, ana iş parçacığı, arka plan programı olmayan evreleri manuel olarak takip etmelidir. Öte yandan, eğer arka plan iş parçacığı kullanıyorsak, ana iş parçacığı bunu tamamen unutabilir ve ana iş parçacığı çıktığında ortadan kalkar. Daemon iş parçacıklarıyla ilgili bir diğer önemli nokta da, onları yalnızca, tamamlanmazsa veya arada öldürülürse bizi etkilemeyecek gerekli olmayan görevler için kullanmayı seçebileceğimizdir. Aşağıda daemon iş parçacıklarının python'da uygulanması verilmiştir -

import threading
import time

def nondaemonThread():
   print("starting my thread")
   time.sleep(8)
   print("ending my thread")
def daemonThread():
   while True:
   print("Hello")
   time.sleep(2)
if __name__ == '__main__':
   nondaemonThread = threading.Thread(target = nondaemonThread)
   daemonThread = threading.Thread(target = daemonThread)
   daemonThread.setDaemon(True)
   daemonThread.start()
   nondaemonThread.start()

Yukarıdaki kodda iki işlev vardır: >nondaemonThread() ve >daemonThread(). İlk işlev durumunu yazdırır ve 8 saniye sonra uyur, deamonThread () işlevi ise her 2 saniyede bir süresiz olarak Hello yazdırır. Aşağıdaki çıktı yardımıyla nondaemon ve daemon iş parçacıkları arasındaki farkı anlayabiliriz -

Hello

starting my thread
Hello
Hello
Hello
Hello
ending my thread
Hello
Hello
Hello
Hello
Hello

İş parçacığı senkronizasyonu, iki veya daha fazla eşzamanlı iş parçacığının aynı anda kritik bölüm olarak bilinen program bölümüne erişmediğinden emin olabileceğimiz bir yöntem olarak tanımlanabilir. Öte yandan, kritik bölümün programın paylaşılan kaynağa erişilen bölümü olduğunu biliyoruz. Dolayısıyla senkronizasyonun, kaynaklara aynı anda erişerek iki veya daha fazla iş parçacığının birbiriyle arayüz oluşturmamasını sağlama işlemi olduğunu söyleyebiliriz. Aşağıdaki şema, bir programın kritik bölümüne aynı anda erişmeye çalışan dört iş parçacığını göstermektedir.

Daha net hale getirmek için, iki veya daha fazla iş parçacığının aynı anda listeye nesneyi eklemeye çalıştığını varsayalım. Bu hareket, nesnelerden birini veya tümünü bırakacağı veya listenin durumunu tamamen bozacağı için başarılı bir sona götürmez. Burada senkronizasyonun rolü, bir seferde yalnızca bir iş parçacığının listeye erişebilmesidir.

İş parçacığı senkronizasyonunda sorunlar

Eşzamanlı programlama uygularken veya senkronizasyon ilkelleri uygularken sorunlarla karşılaşabiliriz. Bu bölümde iki ana konuyu tartışacağız. Sorunlar -

  • Deadlock
  • Yarış kondisyonu

Yarış kondisyonu

Bu, eşzamanlı programlamadaki en önemli sorunlardan biridir. Paylaşılan kaynaklara eşzamanlı erişim, yarış durumuna neden olabilir. Bir yarış koşulu, iki veya daha fazla iş parçacığının paylaşılan verilere erişebildiği ve ardından aynı anda değerini değiştirmeye çalıştığı bir koşulun meydana gelmesi olarak tanımlanabilir. Bu nedenle, değişkenlerin değerleri tahmin edilemez olabilir ve süreçlerin bağlam anahtarlarının zamanlamalarına bağlı olarak değişebilir.

Misal

Yarış durumu kavramını anlamak için bu örneği düşünün -

Step 1 - Bu adımda, iş parçacığı modülünü içe aktarmamız gerekiyor -

import threading

Step 2 - Şimdi, 0 değeriyle birlikte x gibi bir global değişken tanımlayın -

x = 0

Step 3 - Şimdi, tanımlamamız gerekiyor increment_global() fonksiyon, bu global fonksiyonda 1 artış yapacak olan x -

def increment_global():

   global x
   x += 1

Step 4 - Bu adımda, taskofThread()işlev, belirtilen sayıda artış için increment_global () işlevini çağırır; örneğimiz için 50000 kere -

def taskofThread():

   for _ in range(50000):
      increment_global()

Step 5- Şimdi, t1 ve t2 evrelerinin oluşturulduğu main () işlevini tanımlayın. Her ikisi de start () işlevi yardımıyla başlatılacak ve join () işlevi yardımıyla işlerini bitirene kadar bekleyecekler.

def main():
   global x
   x = 0
   
   t1 = threading.Thread(target= taskofThread)
   t2 = threading.Thread(target= taskofThread)

   t1.start()
   t2.start()

   t1.join()
   t2.join()

Step 6- Şimdi, main () işlevini çağırmak istediğimiz kaç yineleme için aralığı vermemiz gerekiyor. Burada 5 defa arıyoruz.

if __name__ == "__main__":
   for i in range(5):
      main()
      print("x = {1} after Iteration {0}".format(i,x))

Aşağıda gösterilen çıktıda, her yinelemeden sonra 100000 beklendiği için x'in değeri olarak yarış koşulunun etkisini görebiliriz. Ancak, değerde çok fazla varyasyon vardır. Bunun nedeni, iş parçacıklarının paylaşılan global değişken x'e eşzamanlı erişimidir.

Çıktı

x = 100000 after Iteration 0
x = 54034 after Iteration 1
x = 80230 after Iteration 2
x = 93602 after Iteration 3
x = 93289 after Iteration 4

Kilitleri kullanarak yarış durumu ile başa çıkmak

Yukarıdaki programda yarış koşulunun etkisini gördüğümüz gibi, birden çok iş parçacığı arasındaki yarış koşulunu ele alabilen bir senkronizasyon aracına ihtiyacımız var. Python'da<threading>modül, yarış koşullarıyla başa çıkmak için Kilit sınıfını sağlar. Dahası,Locksınıfı, birden çok iş parçacığı arasındaki yarış durumunu ele alabileceğimiz farklı yöntemler sağlar. Yöntemler aşağıda açıklanmıştır -

acquire () yöntemi

Bu yöntem, bir kilidi elde etmek, yani bloke etmek için kullanılır. Bir kilit, aşağıdaki doğru veya yanlış değere bağlı olarak bloke edici olabilir veya olmayabilir -

  • With value set to True - accire () yöntemi, varsayılan bağımsız değişken olan True ile çağrılırsa, kilit açılıncaya kadar iş parçacığı yürütmesi engellenir.

  • With value set to False - accire () yöntemi, varsayılan bağımsız değişken olmayan False ile çağrılırsa, iş parçacığı yürütmesi true olarak ayarlanıncaya, yani kilitlenene kadar engellenmez.

release () yöntemi

Bu yöntem bir kilidi serbest bırakmak için kullanılır. Aşağıda, bu yöntemle ilgili birkaç önemli görev verilmiştir -

  • Bir kilit kilitliyse, release()yöntem onun kilidini açar. İşi, birden fazla iş parçacığı engellenirse ve kilidin kilidinin açılmasını beklerse, tam olarak bir iş parçacığının ilerlemesine izin vermektir.

  • Bir ThreadError kilit zaten açıksa.

Şimdi, yarış durumundan kaçınmak için yukarıdaki programı kilit sınıfı ve yöntemleri ile yeniden yazabiliriz. TaskofThread () yöntemini kilit bağımsız değişkeniyle tanımlamamız ve ardından yarış durumundan kaçınmak için kilitlerin engellenmesi ve engellenmemesi için acquire () ve release () yöntemlerini kullanmamız gerekir.

Misal

Aşağıda, yarış durumu ile başa çıkmak için kilit kavramını anlamak için bir python programı örneği verilmiştir -

import threading

x = 0

def increment_global():

   global x
   x += 1

def taskofThread(lock):

   for _ in range(50000):
      lock.acquire()
      increment_global()
      lock.release()

def main():
   global x
   x = 0

   lock = threading.Lock()
   t1 = threading.Thread(target = taskofThread, args = (lock,))
   t2 = threading.Thread(target = taskofThread, args = (lock,))

   t1.start()
   t2.start()

   t1.join()
   t2.join()

if __name__ == "__main__":
   for i in range(5):
      main()
      print("x = {1} after Iteration {0}".format(i,x))

Aşağıdaki çıktı yarış durumunun etkisinin ihmal edildiğini göstermektedir; x'in değeri, her & her yinelemeden sonra, şimdi 100000'dir, bu da bu programın beklentisine göre.

Çıktı

x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4

Çıkmazlar - Yemek Filozofları sorunu

Kilitlenme, eşzamanlı sistemleri tasarlarken karşılaşılabilecek zahmetli bir sorundur. Bu konuyu yemek filozofu probleminin yardımıyla şu şekilde açıklayabiliriz:

Edsger Dijkstra başlangıçta, eşzamanlı sistemin en büyük sorunlarından biri olan kilitlenme denilen ünlü örneklerden biri olan yemek filozofu problemini tanıttı.

Bu problemde yuvarlak bir masada oturup kaselerinden yemek yiyen beş ünlü filozof var. Beş filozofun yemeklerini yemek için kullanabileceği beş çatal vardır. Ancak filozoflar yemeklerini yemek için aynı anda iki çatal kullanmaya karar verirler.

Şimdi filozoflar için iki temel koşul var. Birincisi, filozofların her biri ya yemek yiyor ya da düşünme durumunda olabilir ve ikincisi, önce her iki çatalları, yani sol ve sağ almaları gerekir. Sorun, beş filozofun her biri aynı anda sol çatalı seçmeyi başardığında ortaya çıkar. Şimdi hepsi doğru çatalın bedava olmasını bekliyor, ancak yiyeceklerini yiyene ve doğru çatal asla bulunmayana kadar çatallarını asla bırakmayacaklar. Bu nedenle, yemek masasında bir kilitlenme durumu olacaktır.

Eşzamanlı sistemde kilitlenme

Şimdi görürsek, aynı sorun eşzamanlı sistemlerimizde de ortaya çıkabilir. Yukarıdaki örnekteki çatallar, sistem kaynakları olabilir ve her filozof, kaynakları elde etmek için yarışan süreci temsil edebilir.

Python programı ile çözüm

Bu sorunun çözümü, filozofları iki türe ayırarak bulunabilir: greedy philosophers ve generous philosophers. Esas olarak açgözlü bir filozof, sol çatalı kaldırmaya çalışacak ve orada olana kadar bekleyecektir. Daha sonra sağ çatalın orada olmasını bekleyecek, alacak, yiyecek ve sonra yere koyacaktır. Öte yandan, cömert bir filozof sol çatalı kaldırmaya çalışacak ve eğer orada değilse, bekleyip bir süre sonra tekrar deneyecektir. Sol çatalı alırlarsa, doğru olanı almaya çalışacaklardır. Doğru çatalı da alacaklarsa, her iki çatalı da yiyip bırakacaklar. Ancak, sağ çatalı alamazlarsa, sol çatalı serbest bırakırlar.

Misal

Aşağıdaki Python programı, yemek filozofu sorununa bir çözüm bulmamıza yardımcı olacak -

import threading
import random
import time

class DiningPhilosopher(threading.Thread):

   running = True

   def __init__(self, xname, Leftfork, Rightfork):
   threading.Thread.__init__(self)
   self.name = xname
   self.Leftfork = Leftfork
   self.Rightfork = Rightfork

   def run(self):
   while(self.running):
      time.sleep( random.uniform(3,13))
      print ('%s is hungry.' % self.name)
      self.dine()

   def dine(self):
   fork1, fork2 = self.Leftfork, self.Rightfork

   while self.running:
      fork1.acquire(True)
      locked = fork2.acquire(False)
	  if locked: break
      fork1.release()
      print ('%s swaps forks' % self.name)
      fork1, fork2 = fork2, fork1
   else:
      return

   self.dining()
   fork2.release()
   fork1.release()

   def dining(self):
   print ('%s starts eating '% self.name)
   time.sleep(random.uniform(1,10))
   print ('%s finishes eating and now thinking.' % self.name)

def Dining_Philosophers():
   forks = [threading.Lock() for n in range(5)]
   philosopherNames = ('1st','2nd','3rd','4th', '5th')

   philosophers= [DiningPhilosopher(philosopherNames[i], forks[i%5], forks[(i+1)%5]) \
      for i in range(5)]

   random.seed()
   DiningPhilosopher.running = True
   for p in philosophers: p.start()
   time.sleep(30)
   DiningPhilosopher.running = False
   print (" It is finishing.")

Dining_Philosophers()

Yukarıdaki program, açgözlü ve cömert filozoflar kavramını kullanır. Program ayrıcaacquire() ve release() yöntemleri Lock sınıfı <threading>modül. Çözümü aşağıdaki çıktıda görebiliriz -

Çıktı

4th is hungry.
4th starts eating
1st is hungry.
1st starts eating
2nd is hungry.
5th is hungry.
3rd is hungry.
1st finishes eating and now thinking.3rd swaps forks
2nd starts eating
4th finishes eating and now thinking.
3rd swaps forks5th starts eating
5th finishes eating and now thinking.
4th is hungry.
4th starts eating
2nd finishes eating and now thinking.
3rd swaps forks
1st is hungry.
1st starts eating
4th finishes eating and now thinking.
3rd starts eating
5th is hungry.
5th swaps forks
1st finishes eating and now thinking.
5th starts eating
2nd is hungry.
2nd swaps forks
4th is hungry.
5th finishes eating and now thinking.
3rd finishes eating and now thinking.
2nd starts eating 4th starts eating
It is finishing.

Gerçek hayatta, eğer bir ekip ortak bir görev üzerinde çalışıyorsa, o zaman görevi düzgün bir şekilde bitirmek için aralarında iletişim olmalıdır. Aynı benzetme iplikler için de geçerlidir. Programlamada, işlemcinin ideal süresini azaltmak için birden çok iş parçacığı oluştururuz ve her iş parçacığına farklı alt görevler atarız. Bu nedenle, bir iletişim aracı olmalı ve işi senkronize bir şekilde bitirmek için birbirleriyle etkileşime girmelidirler.

İş parçacığı iç iletişimiyle ilgili aşağıdaki önemli noktaları göz önünde bulundurun -

  • No performance gain - İş parçacıkları ve süreçler arasında doğru iletişimi sağlayamazsak, eşzamanlılık ve paralellikten elde edilen performans kazanımlarının faydası olmaz.

  • Accomplish task properly - İş parçacıkları arasında uygun bir iletişim mekanizması olmadan, atanan görev düzgün bir şekilde tamamlanamaz.

  • More efficient than inter-process communication - İş parçacıkları arası iletişim, süreçler arası iletişimden daha verimli ve kullanımı kolaydır, çünkü bir süreç içindeki tüm iş parçacıkları aynı adres alanını paylaşır ve paylaşılan belleği kullanmaları gerekmez.

İş parçacığı güvenli iletişim için Python veri yapıları

Çok iş parçacıklı kod, bir iş parçacığından başka bir iş parçacığına bilgi geçirme sorunuyla ortaya çıkar. Standart iletişim ilkeleri bu sorunu çözmez. Bu nedenle, iletişimi güvenli hale getirmek için nesneler arasında nesneleri paylaşmak için kendi bileşik nesnemizi uygulamamız gerekir. Aşağıda, bazı değişiklikler yaptıktan sonra iş parçacığı güvenli iletişim sağlayan birkaç veri yapısı bulunmaktadır:

Setleri

Küme veri yapısını iş parçacığı açısından güvenli bir şekilde kullanmak için, kendi kilitleme mekanizmamızı uygulamak için set sınıfını genişletmemiz gerekir.

Misal

İşte sınıfı genişletmenin bir Python örneği -

class extend_class(set):
   def __init__(self, *args, **kwargs):
      self._lock = Lock()
      super(extend_class, self).__init__(*args, **kwargs)

   def add(self, elem):
      self._lock.acquire()
	  try:
      super(extend_class, self).add(elem)
      finally:
      self._lock.release()
  
   def delete(self, elem):
      self._lock.acquire()
      try:
      super(extend_class, self).delete(elem)
      finally:
      self._lock.release()

Yukarıdaki örnekte, adında bir sınıf nesnesi extend_class Python'dan miras alınan tanımlanmıştır set class. Bu sınıfın yapıcısı içinde bir kilit nesnesi oluşturulur. Şimdi, iki işlev var -add() ve delete(). Bu işlevler tanımlanmıştır ve iş parçacığı açısından güvenlidir. İkisi de güveniyorsuper bir anahtar istisna dışında sınıf işlevselliği.

Dekoratör

Bu, iş parçacığı güvenli iletişim için başka bir anahtar yöntem, dekoratörlerin kullanılmasıdır.

Misal

Dekoratörlerin nasıl kullanılacağını gösteren bir Python örneği düşünün & mminus;

def lock_decorator(method):

   def new_deco_method(self, *args, **kwargs):
      with self._lock:
         return method(self, *args, **kwargs)
return new_deco_method

class Decorator_class(set):
   def __init__(self, *args, **kwargs):
      self._lock = Lock()
      super(Decorator_class, self).__init__(*args, **kwargs)

   @lock_decorator
   def add(self, *args, **kwargs):
      return super(Decorator_class, self).add(elem)
   @lock_decorator
   def delete(self, *args, **kwargs):
      return super(Decorator_class, self).delete(elem)

Yukarıdaki örnekte, Python yöntem sınıfından daha fazla miras alınan lock_decorator adlı bir dekoratör yöntemi tanımlanmıştır. Daha sonra bu sınıfın yapıcısı içinde bir kilit nesnesi oluşturulur. Şimdi iki işlev var - ekle () ve sil (). Bu işlevler tanımlanmıştır ve iş parçacığı açısından güvenlidir. Her ikisi de bir anahtar istisna dışında süper sınıf işlevselliğe güveniyor.

Listeler

Liste veri yapısı iş parçacığı açısından güvenlidir, hızlıdır ve geçici, bellek içi depolama için kolay bir yapıdır. Cpython'da, GIL bunlara eş zamanlı erişime karşı koruma sağlar. Listelerin iş parçacığı açısından güvenli olduğunu öğrendiğimizde, peki ya içlerinde yatan veriler. Aslında listenin verileri korunmuyor. Örneğin,L.append(x)başka bir iş parçacığı aynı şeyi yapmaya çalışıyorsa beklenen sonucu döndürme garantisi verilmez. Bunun nedeni,append() atomik bir işlemdir ve iş parçacığı güvenlidir, ancak diğer iş parçacığı listenin verilerini eşzamanlı olarak değiştirmeye çalışıyor, bu nedenle yarış koşullarının çıktı üzerindeki yan etkilerini görebiliyoruz.

Bu tür bir sorunu çözmek ve verileri güvenli bir şekilde değiştirmek için, birden fazla iş parçacığının potansiyel olarak yarış koşullarında çalışmamasını sağlayan uygun bir kilitleme mekanizması uygulamalıyız. Doğru kilitleme mekanizmasını uygulamak için, önceki örneklerde yaptığımız gibi sınıfı genişletebiliriz.

Listelerdeki diğer bazı atomik işlemler aşağıdaki gibidir -

L.append(x)
L1.extend(L2)
x = L[i]
x = L.pop()
L1[i:j] = L2
L.sort()
x = y
x.field = y
D[x] = y
D1.update(D2)
D.keys()

Burada -

  • L, L1, L2 hepsi listedir
  • D, D1, D2 diktedir
  • x, y nesnelerdir
  • i, j ints

Kuyruklar

Listenin verileri korunmuyorsa, sonuçlarla yüzleşmek zorunda kalabiliriz. Yarış koşullarının yanlış veri öğesini alabilir veya silebiliriz. Bu nedenle kuyruk veri yapısının kullanılması önerilir. Gerçek dünyadaki kuyruk örneği, aracın ilk girdiği, ilk çıktığı tek şeritli tek yönlü bir yol olabilir. Bilet gişelerindeki ve otobüs duraklarındaki kuyruklardan daha fazla gerçek dünya örneği görülebilir.

Kuyruklar varsayılan olarak iş parçacığı güvenli veri yapılarıdır ve karmaşık kilitleme mekanizmasını uygulama konusunda endişelenmemize gerek yoktur. Python bize modülümüzde farklı kuyruk türlerini kullanabilirsiniz.

Kuyruk Türleri

Bu bölümde, farklı kuyruk türleri hakkında kazanacağız. Python, siteden kullanmak için üç sıra seçeneği sağlar.<queue> modül -

  • Normal Kuyruklar (FIFO, İlk Giren İlk Çıkar)
  • LIFO, Son Giren İlk Çıkar
  • Priority

Sonraki bölümlerde farklı kuyruklar hakkında bilgi edineceğiz.

Normal Kuyruklar (FIFO, İlk Giren İlk Çıkar)

Python tarafından sunulan en yaygın kullanılan kuyruk uygulamalarıdır. Bu kuyruk mekanizmasında kim önce gelirse önce hizmeti alır. FIFO, normal kuyruklar olarak da adlandırılır. FIFO kuyrukları aşağıdaki gibi gösterilebilir -

FIFO Kuyruğunun Python Uygulaması

Python'da, FIFO kuyruğu tek iş parçacığı ve çoklu iş parçacığı ile uygulanabilir.

Tek iş parçacıklı FIFO kuyruğu

FIFO kuyruğunu tek iş parçacığı ile uygulamak için, Queuesınıfı, temel bir ilk giren ilk çıkar kapsayıcı uygulayacaktır. Öğeler, dizinin bir "sonuna" eklenecekput()ve kullanılarak diğer ucundan kaldırıldı get().

Misal

Aşağıda, tek iş parçacığı ile FIFO kuyruğunun uygulanması için bir Python programı verilmiştir -

import queue

q = queue.Queue()

for i in range(8):
   q.put("item-" + str(i))

while not q.empty():
   print (q.get(), end = " ")

Çıktı

item-0 item-1 item-2 item-3 item-4 item-5 item-6 item-7

Çıktı, yukarıdaki programın, öğelerin yerleştirildikleri sırayla kuyruktan kaldırıldığını göstermek için tek bir iş parçacığı kullandığını gösterir.

Birden çok iş parçacığı içeren FIFO kuyruğu

FIFO'yu birden çok iş parçacığı ile uygulamak için, kuyruk modülünden genişletilen myqueue () işlevini tanımlamamız gerekir. Get () ve put () yöntemlerinin çalışması, FIFO kuyruğunu tek iş parçacığı ile uygularken yukarıda tartışılanla aynıdır. Daha sonra onu çok iş parçacıklı hale getirmek için, evreleri bildirmemiz ve başlatmamız gerekir. Bu iş parçacıkları kuyruğu FIFO tarzında tüketecektir.

Misal

Aşağıda, birden çok iş parçacığı içeren FIFO kuyruğunun uygulanması için bir Python programı verilmiştir

import threading
import queue
import random
import time
def myqueue(queue):
   while not queue.empty():
   item = queue.get()
   if item is None:
   break
   print("{} removed {} from the queue".format(threading.current_thread(), item))
   queue.task_done()
   time.sleep(2)
q = queue.Queue()
for i in range(5):
   q.put(i)
threads = []
for i in range(4):
   thread = threading.Thread(target=myqueue, args=(q,))
   thread.start()
   threads.append(thread)
for thread in threads:
   thread.join()

Çıktı

<Thread(Thread-3654, started 5044)> removed 0 from the queue
<Thread(Thread-3655, started 3144)> removed 1 from the queue
<Thread(Thread-3656, started 6996)> removed 2 from the queue
<Thread(Thread-3657, started 2672)> removed 3 from the queue
<Thread(Thread-3654, started 5044)> removed 4 from the queue

LIFO, İlk Giren Son Sırada

Bu kuyruk, FIFO (İlk Giren İlk Çıkar) kuyruklarından tamamen zıt bir analoji kullanır. Bu kuyruk mekanizmasında en son gelen, önce servisi alacaktır. Bu, yığın veri yapısını uygulamaya benzer. LIFO kuyrukları, yapay zeka algoritmaları gibi Derinlik öncelikli arama uygularken yararlı olduğunu kanıtladı.

LIFO kuyruğunun Python uygulaması

Python'da, LIFO kuyruğu tek iş parçacığı ve çoklu iş parçacığı ile uygulanabilir.

Tek iş parçacıklı LIFO kuyruğu

LIFO kuyruğunu tek iş parçacığı ile uygulamak için, Queue sınıf, yapıyı kullanarak temel bir son giren ilk çıkar kapsayıcı uygulayacak Queue.LifoQueue. Şimdi aradığımdaput(), elemanlar kabın kafasına eklenir ve kullanımda da kafadan çıkarılır. get().

Misal

Aşağıda, LIFO kuyruğunun tek iş parçacığı ile uygulanması için bir Python programı verilmiştir -

import queue

q = queue.LifoQueue()

for i in range(8):
   q.put("item-" + str(i))

while not q.empty():
   print (q.get(), end=" ")
Output:
item-7 item-6 item-5 item-4 item-3 item-2 item-1 item-0

Çıktı, yukarıdaki programın, öğelerin eklendikleri sıranın tersi sırayla kuyruktan kaldırıldığını göstermek için tek bir iş parçacığı kullandığını gösterir.

Birden çok iş parçacığı ile LIFO kuyruğu

Uygulama, FIFO kuyruklarının çoklu iş parçacığı ile uygulanmasını yaptığımız gibi benzerdir. Tek fark, kullanmamız gerektiğidir.Queue yapıyı kullanarak temel bir son giren ilk çıkar kapsayıcı uygulayacak sınıf Queue.LifoQueue.

Misal

Aşağıda, LIFO kuyruğunun birden çok iş parçacığı ile uygulanması için bir Python programı verilmiştir -

import threading
import queue
import random
import time
def myqueue(queue):
   while not queue.empty():
      item = queue.get()
      if item is None:
      break
	  print("{} removed {} from the queue".format(threading.current_thread(), item))
      queue.task_done()
      time.sleep(2)
q = queue.LifoQueue()
for i in range(5):
   q.put(i)
threads = []
for i in range(4):
   thread = threading.Thread(target=myqueue, args=(q,))
   thread.start()
   threads.append(thread)
for thread in threads:
   thread.join()

Çıktı

<Thread(Thread-3882, started 4928)> removed 4 from the queue
<Thread(Thread-3883, started 4364)> removed 3 from the queue
<Thread(Thread-3884, started 6908)> removed 2 from the queue
<Thread(Thread-3885, started 3584)> removed 1 from the queue
<Thread(Thread-3882, started 4928)> removed 0 from the queue

Öncelik sırası

FIFO ve LIFO kuyruklarında, öğelerin sıralaması yerleştirme sırasına bağlıdır. Ancak, önceliğin yerleştirme sırasından daha önemli olduğu birçok durum vardır. Gerçek dünya örneğini ele alalım. Havaalanındaki güvenliğin farklı kategorilerdeki insanları kontrol ettiğini varsayalım. VVIP çalışanları, havayolu personeli, gümrük memuru, kategoriler sıradan kişiler için olduğu gibi varış bazında kontrol edilmek yerine öncelikli olarak kontrol edilebilir.

Öncelik kuyruğu için dikkate alınması gereken bir diğer önemli husus, bir görev planlayıcısının nasıl geliştirileceğidir. Yaygın bir tasarım, kuyruktaki en ajan görevini öncelik temelinde sunmaktır. Bu veri yapısı, öncelik değerlerine göre öğeleri kuyruktan almak için kullanılabilir.

Öncelik Kuyruğunun Python Uygulaması

Python'da, öncelik kuyruğu tek iş parçacığı ve çoklu iş parçacığı ile uygulanabilir.

Tek iş parçacığı ile öncelik sırası

Tek iş parçacığı ile öncelik kuyruğu uygulamak için, Queue sınıf, yapıyı kullanarak öncelikli kapsayıcıda bir görev uygulayacak Queue.PriorityQueue. Şimdi aradığımdaput()öğeler, en düşük değerin en yüksek önceliğe sahip olacağı bir değerle eklenir ve bu nedenle ilk olarak kullanılarak alınır get().

Misal

Tek iş parçacığı ile Öncelik kuyruğunun uygulanması için aşağıdaki Python programını düşünün -

import queue as Q
p_queue = Q.PriorityQueue()

p_queue.put((2, 'Urgent'))
p_queue.put((1, 'Most Urgent'))
p_queue.put((10, 'Nothing important'))
prio_queue.put((5, 'Important'))

while not p_queue.empty():
   item = p_queue.get()
   print('%s - %s' % item)

Çıktı

1 – Most Urgent
2 - Urgent
5 - Important
10 – Nothing important

Yukarıdaki çıktıda, sıranın öğeleri önceliğe göre sakladığını görebiliriz - daha az değer yüksek önceliğe sahip.

Çoklu iş parçacıklı öncelik sırası

Uygulama, birden çok iş parçacığı içeren FIFO ve LIFO kuyruklarının uygulanmasına benzer. Tek fark, kullanmamız gerektiğidir.Queue yapıyı kullanarak önceliği başlatmak için sınıf Queue.PriorityQueue. Diğer bir fark, kuyruğun oluşturulma şeklidir. Aşağıda verilen örnekte, iki özdeş veri seti ile üretilecektir.

Misal

Aşağıdaki Python programı, birden çok iş parçacığı ile öncelik sırasının uygulanmasına yardımcı olur -

import threading
import queue
import random
import time
def myqueue(queue):
   while not queue.empty():
      item = queue.get()
      if item is None:
      break
      print("{} removed {} from the queue".format(threading.current_thread(), item))
      queue.task_done()
      time.sleep(1)
q = queue.PriorityQueue()
for i in range(5):
   q.put(i,1)

for i in range(5):
   q.put(i,1)

threads = []
for i in range(2):
   thread = threading.Thread(target=myqueue, args=(q,))
   thread.start()
   threads.append(thread)
for thread in threads:
   thread.join()

Çıktı

<Thread(Thread-4939, started 2420)> removed 0 from the queue
<Thread(Thread-4940, started 3284)> removed 0 from the queue
<Thread(Thread-4939, started 2420)> removed 1 from the queue
<Thread(Thread-4940, started 3284)> removed 1 from the queue
<Thread(Thread-4939, started 2420)> removed 2 from the queue
<Thread(Thread-4940, started 3284)> removed 2 from the queue
<Thread(Thread-4939, started 2420)> removed 3 from the queue
<Thread(Thread-4940, started 3284)> removed 3 from the queue
<Thread(Thread-4939, started 2420)> removed 4 from the queue
<Thread(Thread-4940, started 3284)> removed 4 from the queue

Bu bölümde, iş parçacığı uygulamalarının test edilmesini öğreneceğiz. Ayrıca test etmenin önemini de öğreneceğiz.

Neden Test Edilmeli?

Test etmenin önemi hakkındaki tartışmaya girmeden önce, testin ne olduğunu bilmemiz gerekir. Genel anlamda test, bir şeyin ne kadar iyi çalıştığını bulmanın bir tekniğidir. Öte yandan, özellikle bilgisayar programları veya yazılımları hakkında konuşursak, test, bir yazılım programının işlevselliğine erişme tekniğidir.

Bu bölümde, yazılım testinin önemini tartışacağız. Yazılım geliştirmede, yazılımın istemciye yayınlanmasından önce iki kez kontrol edilmelidir. Bu nedenle yazılımı deneyimli test ekibi tarafından test etmek çok önemlidir. Yazılım testinin önemini anlamak için aşağıdaki noktaları göz önünde bulundurun -

Yazılım kalitesinin iyileştirilmesi

Elbette hiçbir şirket düşük kaliteli yazılım sunmak istemez ve hiçbir müşteri düşük kaliteli yazılım satın almak istemez. Test, içindeki hataları bulup düzelterek yazılımın kalitesini artırır.

Müşteri memnuniyeti

Herhangi bir işin en önemli kısmı müşterilerinin memnuniyetidir. Firmalar hatasız ve kaliteli yazılımlar sunarak müşteri memnuniyetini sağlayabilirler.

Yeni özelliklerin etkisini azaltın

10000 satırlık bir yazılım sistemi yaptığımızı ve yeni bir özellik eklememiz gerektiğini varsayalım, o zaman geliştirme ekibi bu yeni özelliğin tüm yazılım üzerindeki etkisi hakkında endişelenecektir. Burada da testler hayati bir rol oynar çünkü eğer test ekibi iyi bir test paketi yapmışsa, bizi herhangi bir olası felaket kırılmasından kurtarabilir.

Kullanıcı deneyimi

Herhangi bir işin bir diğer en önemli kısmı, o ürünü kullananların deneyimidir. Yalnızca test, son kullanıcının ürünü kullanmayı basit ve kolay bulmasını sağlayabilir.

Giderleri azaltmak

Test, teslimattan sonra düzeltmek yerine geliştirmenin test aşamasında hataları bularak ve düzelterek yazılımın toplam maliyetini düşürebilir. Yazılımın teslim edilmesinden sonra büyük bir hata varsa, o zaman maddi maliyetini giderler ve maddi olmayan maliyetler, müşteri memnuniyetsizliği, şirketin olumsuz itibarı vb.

Ne Test Edilecek?

Her zaman neyin test edileceğine dair uygun bilgiye sahip olunması önerilir. Bu bölümde, herhangi bir yazılımı test ederken önce test cihazının ana nedeni olduğunu anlayacağız. Kod kapsamı, yani, test paketimizin test sırasında kaç satır kodla karşılaştığından kaçınılmalıdır. Bunun nedeni, test sırasında yalnızca kod satırlarının sayısına odaklanmak sistemimize gerçek bir değer katmamaktadır. Daha sonraki bir aşamada konuşlandırmadan sonra bile yansıyan bazı hatalar kalabilir.

Neyin test edileceğiyle ilgili aşağıdaki önemli noktaları göz önünde bulundurun:

  • Kod kapsamı yerine kodun işlevselliğini test etmeye odaklanmalıyız.

  • Önce kodun en önemli kısımlarını test etmemiz ve ardından kodun daha az önemli kısımlarına doğru ilerlememiz gerekir. Kesinlikle zaman kazandıracak.

  • Test cihazı, yazılımı sınırlarına kadar zorlayabilecek çok sayıda farklı teste sahip olmalıdır.

Eşzamanlı yazılım programlarını test etme yaklaşımları

Çok çekirdekli mimarinin gerçek yeteneğini kullanma yeteneği nedeniyle, eşzamanlı yazılım sistemleri sıralı sistemlerin yerini alıyor. Son zamanlarda cep telefonlarından çamaşır makinelerine, arabalardan uçaklara vb. Her şeyde eşzamanlı sistem programları kullanılıyor. zaten bir hata varsa, birden fazla hatayla sonuçlanırdık.

Eşzamanlı yazılım programları için test teknikleri, yarış koşulları, kilitlenmeler ve atomikliğin ihlali gibi potansiyel olarak zararlı modelleri ortaya çıkaran serpiştirmeyi seçmeye yoğun bir şekilde odaklanmaktadır. Eşzamanlı yazılım programlarını test etmek için iki yaklaşım aşağıda verilmiştir:

Sistematik keşif

Bu yaklaşım, harmanlamaların alanını olabildiğince geniş bir şekilde keşfetmeyi amaçlamaktadır. Bu tür yaklaşımlar, bir kaba kuvvet tekniğini benimseyebilir ve diğerleri, harmanlamaların uzayını keşfetmek için kısmi düzen azaltma tekniğini veya sezgisel tekniği benimser.

Mülkiyet odaklı

Mülkiyet odaklı yaklaşımlar, eşzamanlılık hatalarının şüpheli bellek erişim modeli gibi belirli özellikleri açığa çıkaran serpiştirmeler altında oluşma olasılığının daha yüksek olduğu gözlemine dayanır. Farklı mülkiyet odaklı yaklaşımlar, yarış koşulları, kilitlenmeler ve atomikliğin ihlali gibi farklı hataları hedefler ve bunlar ayrıca bir veya diğer belirli özelliklere bağlıdır.

Test Stratejileri

Test Stratejisi, test yaklaşımı olarak da bilinir. Strateji, testin nasıl yürütüleceğini tanımlar. Test yaklaşımının iki tekniği vardır -

Proaktif

Derleme oluşturulmadan önce hataları bulmak ve düzeltmek için test tasarım sürecinin olabildiğince erken başlatıldığı bir yaklaşım.

Reaktif

Geliştirme süreci tamamlanana kadar testin başlamadığı bir yaklaşım.

Python programına herhangi bir test stratejisi veya yaklaşım uygulamadan önce, bir yazılım programının sahip olabileceği hatalar hakkında temel bir fikre sahip olmamız gerekir. Hatalar aşağıdaki gibidir -

Sözdizimsel hatalar

Program geliştirme sırasında çok sayıda küçük hata olabilir. Hatalar çoğunlukla yazım hatalarından kaynaklanmaktadır. Örneğin, eksik iki nokta üst üste veya bir anahtar kelimenin yanlış yazımı, vb. Bu tür hatalar, mantıktaki değil, program sözdizimindeki hatadan kaynaklanır. Bu nedenle, bu hatalara sözdizimsel hatalar denir.

Anlamsal hatalar

Anlamsal hatalara mantıksal hatalar da denir. Yazılım programında mantıksal veya anlamsal bir hata varsa, ifade doğru bir şekilde derlenecek ve çalışacaktır ancak mantık doğru olmadığı için istenen çıktıyı vermeyecektir.

Birim Testi

Bu, python programlarını test etmek için en çok kullanılan test stratejilerinden biridir. Bu strateji, kodun birimlerini veya bileşenlerini test etmek için kullanılır. Birimler veya bileşenler derken, kodun sınıflarını veya işlevlerini kastediyoruz. Birim testi, "küçük" birimleri test ederek büyük programlama sistemlerinin testini basitleştirir. Yukarıdaki konseptin yardımıyla, birim testi, istenen çıktıyı döndürüp döndürmediklerini belirlemek için tek tek kaynak kod birimlerinin test edildiği bir yöntem olarak tanımlanabilir.

Sonraki bölümlerimizde, birim testi için farklı Python modülleri hakkında bilgi edineceğiz.

unittest modülü

Birim testi için ilk modül, birim testi modülüdür. JUnit'ten esinlenmiştir ve varsayılan olarak Python3.6'ya dahildir. Test otomasyonunu, testler için kurulum ve kapatma kodunun paylaşılmasını, testlerin koleksiyonlarda toplanmasını ve testlerin raporlama çerçevesinden bağımsızlığını destekler.

Aşağıda, unittest modülü tarafından desteklenen birkaç önemli kavram yer almaktadır.

Metin fikstürü

Teste başlamadan önce çalıştırılabilmesi ve test bittikten sonra yırtılabilmesi için bir test kurmak için kullanılır. Teste başlamadan önce ihtiyaç duyulan geçici veritabanı, dizin vb. Oluşturulmasını içerebilir.

Test durumu

Test senaryosu, gerekli bir yanıtın belirli bir girdi setinden gelip gelmediğini kontrol eder. Unittest modülü, yeni test senaryoları oluşturmak için kullanılabilecek TestCase adlı bir temel sınıf içerir. Varsayılan olarak iki yöntem içerir -

  • setUp()- egzersiz yapmadan önce test fikstürünü kurmak için bir kanca yöntemi. Bu, uygulanan test yöntemleri çağrılmadan önce çağrılır.

  • tearDown( - sınıftaki tüm testleri çalıştırdıktan sonra sınıf fikstürünü yeniden yapılandırmak için bir kanca yöntemi.

Test odası

Test paketleri, test senaryoları veya her ikisinden oluşan bir koleksiyondur.

Test koşucusu

Test senaryolarının veya giysilerinin çalışmasını kontrol eder ve sonucu kullanıcıya sağlar. Sonucu sağlamak için GUI veya basit metin arabirimi kullanabilir.

Example

Aşağıdaki Python programı, adlı bir modülü test etmek için unittest modülünü kullanır. Fibonacci. Program, bir sayının Fibonacci serisinin hesaplanmasına yardımcı olur. Bu örnekte, farklı yöntemler kullanarak test senaryolarını tanımlamak için Fibo_test adlı bir sınıf oluşturduk. Bu yöntemler, unittest.TestCase'den miras alınır. Varsayılan olarak iki yöntem kullanıyoruz - setUp () ve tearDown (). Ayrıca testfibocal yöntemi de tanımlıyoruz. Testin adı harf testi ile başlamalıdır. Son blokta, unittest.main () test betiğine bir komut satırı arayüzü sağlar.

import unittest
def fibonacci(n):
   a, b = 0, 1
   for i in range(n):
   a, b = b, a + b
   return a
class Fibo_Test(unittest.TestCase):
   def setUp(self):
   print("This is run before our tests would be executed")
   def tearDown(self):
   print("This is run after the completion of execution of our tests")

   def testfibocal(self):
   self.assertEqual(fib(0), 0)
   self.assertEqual(fib(1), 1)
   self.assertEqual(fib(5), 5)
   self.assertEqual(fib(10), 55)
   self.assertEqual(fib(20), 6765)

if __name__ == "__main__":
   unittest.main()

Komut satırından çalıştırıldığında, yukarıdaki komut dosyası şuna benzer bir çıktı üretir -

Çıktı

This runs before our tests would be executed.
This runs after the completion of execution of our tests.
.
----------------------------------------------------------------------
Ran 1 test in 0.006s
OK

Şimdi, daha açık hale getirmek için, Fibonacci modülünün tanımlanmasına yardımcı olan kodumuzu değiştiriyoruz.

Aşağıdaki kod bloğunu bir örnek olarak düşünün -

def fibonacci(n):
   a, b = 0, 1
   for i in range(n):
   a, b = b, a + b
   return a

Aşağıda gösterildiği gibi kod bloğunda birkaç değişiklik yapılmıştır -

def fibonacci(n):
   a, b = 1, 1
   for i in range(n):
   a, b = b, a + b
   return a

Şimdi, betiği değiştirilen kodla çalıştırdıktan sonra, aşağıdaki çıktıyı alacağız -

This runs before our tests would be executed.
This runs after the completion of execution of our tests.
F
======================================================================
FAIL: testCalculation (__main__.Fibo_Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "unitg.py", line 15, in testCalculation
self.assertEqual(fib(0), 0)
AssertionError: 1 != 0
----------------------------------------------------------------------
Ran 1 test in 0.007s

FAILED (failures = 1)

Yukarıdaki çıktı, modülün istenen çıktıyı veremediğini gösterir.

Docktest modülü

Docktest modülü ayrıca birim testinde yardımcı olur. Ayrıca python ile önceden paketlenmiş olarak gelir. Birim test modülüne göre kullanımı daha kolaydır. Unittest modülü karmaşık testler için daha uygundur. Doctest modülünü kullanmak için onu içe aktarmamız gerekiyor. Karşılık gelen işlevin docstring'i, çıktılarıyla birlikte etkileşimli python oturumuna sahip olmalıdır.

Kodumuzda her şey yolundaysa, docktest modülünden çıktı alınmayacaktır; aksi takdirde çıktı sağlayacaktır.

Misal

Aşağıdaki Python örneği, bir sayının Fibonacci serisini hesaplamaya yardımcı olan Fibonacci adlı bir modülü test etmek için docktest modülünü kullanır.

import doctest
def fibonacci(n):
   """
   Calculates the Fibonacci number

   >>> fibonacci(0)
   0
   >>> fibonacci(1)
   1
   >>> fibonacci(10)
   55
   >>> fibonacci(20)
   6765
   >>>

   """
   a, b = 1, 1
   for i in range(n):
   a, b = b, a + b
   return a
      if __name__ == "__main__":
   doctest.testmod()

Fib adlı karşılık gelen işlevin docstringinin çıktılarla birlikte etkileşimli python oturumuna sahip olduğunu görebiliriz. Kodumuz iyi ise, doctest modülünden hiçbir çıktı alınamayacaktır. Ancak nasıl çalıştığını görmek için -v seçeneğiyle çalıştırabiliriz.

(base) D:\ProgramData>python dock_test.py -v
Trying:
   fibonacci(0)
Expecting:
   0
ok
Trying:
   fibonacci(1)
Expecting:
   1
ok
Trying:
   fibonacci(10)
Expecting:
   55
ok
Trying:
   fibonacci(20)
Expecting:
   6765
ok
1 items had no tests:
   __main__
1 items passed all tests:
4 tests in __main__.fibonacci
4 tests in 2 items.
4 passed and 0 failed.
Test passed.

Şimdi, Fibonacci modülünün tanımlanmasına yardımcı olan kodu değiştireceğiz

Aşağıdaki kod bloğunu bir örnek olarak düşünün -

def fibonacci(n):
   a, b = 0, 1
   for i in range(n):
   a, b = b, a + b
   return a

Aşağıdaki kod bloğu değişikliklere yardımcı olur -

def fibonacci(n):
   a, b = 1, 1
   for i in range(n):
   a, b = b, a + b
   return a

Betiği –v seçeneği olmadan bile değiştirilen kodla çalıştırdıktan sonra, çıktıyı aşağıda gösterildiği gibi alacağız.

Çıktı

(base) D:\ProgramData>python dock_test.py
**********************************************************************
File "unitg.py", line 6, in __main__.fibonacci
Failed example:
   fibonacci(0)
Expected:
   0
Got:
   1
**********************************************************************
File "unitg.py", line 10, in __main__.fibonacci
Failed example:
   fibonacci(10)
Expected:
   55
Got:
   89
**********************************************************************
File "unitg.py", line 12, in __main__.fibonacci
Failed example:
   fibonacci(20)
Expected:
   6765
Got:
   10946
**********************************************************************
1 items had failures:
   3 of 4 in __main__.fibonacci
***Test Failed*** 3 failures.

Yukarıdaki çıktıda üç testin başarısız olduğunu görebiliyoruz.

Bu bölümde, iş parçacığı uygulamalarında nasıl hata ayıklanacağını öğreneceğiz. Ayrıca hata ayıklamanın önemini de öğreneceğiz.

Hata Ayıklama nedir?

Bilgisayar programlamasında hata ayıklama, bilgisayar programındaki hataları, hataları ve anormallikleri bulma ve kaldırma işlemidir. Bu süreç, kod yazılır yazılmaz başlar ve kod bir yazılım ürünü oluşturmak için diğer programlama birimleriyle birleştirildiği için birbirini takip eden aşamalarda devam eder. Hata ayıklama, yazılım test sürecinin bir parçasıdır ve tüm yazılım geliştirme yaşam döngüsünün ayrılmaz bir parçasıdır.

Python Hata Ayıklayıcı

Python hata ayıklayıcı veya pdbPython standart kitaplığının bir parçasıdır. Bulunması zor hataları izlemek için iyi bir geri dönüş aracıdır ve hatalı kodu hızlı ve güvenilir bir şekilde düzeltmemizi sağlar. Aşağıdakiler en önemli iki görevdir.pdp hata ayıklayıcı -

  • Çalışma zamanında değişkenlerin değerlerini kontrol etmemizi sağlar.
  • Kodda ilerleyebilir ve kesme noktaları da belirleyebiliriz.

Pdb ile aşağıdaki iki şekilde çalışabiliriz -

  • Komut satırı aracılığıyla; buna postmortem hata ayıklama da denir.
  • Etkileşimli pdb çalıştırarak.

Pdb ile çalışmak

Python hata ayıklayıcı ile çalışmak için, hata ayıklayıcıya girmek istediğimiz konumda aşağıdaki kodu kullanmamız gerekir -

import pdb;
pdb.set_trace()

Komut satırı üzerinden pdb ile çalışmak için aşağıdaki komutları göz önünde bulundurun.

  • h(help)
  • d(down)
  • u(up)
  • b(break)
  • cl(clear)
  • l(list))
  • n(next))
  • c(continue)
  • s(step)
  • r(return))
  • b(break)

Aşağıda Python hata ayıklayıcısının h (yardım) komutunun bir demosu yer almaktadır -

import pdb

pdb.set_trace()
--Call--
>d:\programdata\lib\site-packages\ipython\core\displayhook.py(247)__call__()
-> def __call__(self, result = None):
(Pdb) h

Documented commands (type help <topic>):
========================================
EOF   c         d       h        list     q       rv      undisplay
a     cl        debug   help     ll       quit    s       unt
alias clear     disable ignore   longlist r       source  until
args  commands  display interact n        restart step    up
b     condition down    j        next     return  tbreak  w
break cont      enable  jump     p        retval  u       whatis
bt    continue  exit    l        pp       run     unalias where

Miscellaneous help topics:
==========================
exec pdb

Misal

Python hata ayıklayıcı ile çalışırken, aşağıdaki satırları kullanarak kesme noktasını komut dosyasının herhangi bir yerinde ayarlayabiliriz -

import pdb;
pdb.set_trace()

Kesme noktasını ayarladıktan sonra, betiği normal şekilde çalıştırabiliriz. Komut dosyası belirli bir noktaya kadar çalışacaktır; bir hat ayarlanana kadar. Komut dosyasının çeşitli yerlerinde yukarıda belirtilen satırları kullanarak komut dosyasını çalıştıracağımız aşağıdaki örneği düşünün:

import pdb;
a = "aaa"
pdb.set_trace()
b = "bbb"
c = "ccc"
final = a + b + c
print (final)

Yukarıdaki script çalıştırıldığında, programı a = “aaa” olana kadar çalıştıracaktır, aşağıdaki çıktıda bunu kontrol edebiliriz.

Çıktı

--Return--
> <ipython-input-7-8a7d1b5cc854>(3)<module>()->None
-> pdb.set_trace()
(Pdb) p a
'aaa'
(Pdb) p b
*** NameError: name 'b' is not defined
(Pdb) p c
*** NameError: name 'c' is not defined

Pdb'de 'p (yazdır)' komutunu kullandıktan sonra, bu komut dosyası yalnızca 'aaa' yazdırıyor. Bunu bir hata izliyor çünkü kesme noktasını a = "aaa" olana kadar ayarladık.

Benzer şekilde, kesme noktalarını değiştirerek komut dosyasını çalıştırabilir ve çıktıdaki farkı görebiliriz -

import pdb
a = "aaa"
b = "bbb"
c = "ccc"
pdb.set_trace()
final = a + b + c
print (final)

Çıktı

--Return--
> <ipython-input-9-a59ef5caf723>(5)<module>()->None
-> pdb.set_trace()
(Pdb) p a
'aaa'
(Pdb) p b
'bbb'
(Pdb) p c
'ccc'
(Pdb) p final
*** NameError: name 'final' is not defined
(Pdb) exit

Aşağıdaki komut dosyasında, programın son satırında kesme noktasını ayarlıyoruz -

import pdb
a = "aaa"
b = "bbb"
c = "ccc"
final = a + b + c
pdb.set_trace()
print (final)

Çıktı aşağıdaki gibidir -

--Return--
> <ipython-input-11-8019b029997d>(6)<module>()->None
-> pdb.set_trace()
(Pdb) p a
'aaa'
(Pdb) p b
'bbb'
(Pdb) p c
'ccc'
(Pdb) p final
'aaabbbccc'
(Pdb)

Bu bölümde, kıyaslama ve profil oluşturmanın performans sorunlarını ele almaya nasıl yardımcı olduğunu öğreneceğiz.

Diyelim ki bir kod yazdık ve bu da istenen sonucu veriyor ama ya bu kodu biraz daha hızlı çalıştırmak istiyorsak, çünkü ihtiyaçlar değişti. Bu durumda, kodumuzun hangi kısımlarının tüm programı yavaşlattığını bulmamız gerekir. Bu durumda, kıyaslama ve profil oluşturma faydalı olabilir.

Kıyaslama nedir?

Kıyaslama, bir şeyi bir standartla karşılaştırarak değerlendirmeyi amaçlar. Ancak burada ortaya çıkan soru, kıyaslamanın ne olacağı ve yazılım programlama durumunda buna neden ihtiyacımız olduğudur. Kodun karşılaştırılması, kodun ne kadar hızlı yürütüldüğü ve darboğazın nerede olduğu anlamına gelir. Kıyaslamanın temel nedenlerinden biri kodu optimize etmesidir.

Kıyaslama nasıl çalışır?

Kıyaslamanın çalışması hakkında konuşursak, tüm programı tek bir mevcut durum olarak kıyaslayarak başlamalıyız, ardından mikro kıyaslamaları birleştirebilir ve ardından bir programı daha küçük programlara ayırabiliriz. Programımızdaki darboğazları bulmak ve optimize etmek için. Başka bir deyişle, büyük ve zor problemi, onları optimize etmek için daha küçük ve biraz daha kolay problemler dizisine ayırmak olarak anlayabiliriz.

Kıyaslama için Python modülü

Python'da, karşılaştırma için varsayılan olarak adlandırılan bir modülümüz var timeit. Yardımıylatimeit modülünde, ana programımızdaki küçük Python kodunun performansını ölçebiliriz.

Misal

Aşağıdaki Python betiğinde, timeit iki işlevi yerine getirmek için harcanan zamanı daha da ölçen modül - functionA ve functionB -

import timeit
import time
def functionA():
   print("Function A starts the execution:")
   print("Function A completes the execution:")
def functionB():
   print("Function B starts the execution")
   print("Function B completes the execution")
start_time = timeit.default_timer()
functionA()
print(timeit.default_timer() - start_time)
start_time = timeit.default_timer()
functionB()
print(timeit.default_timer() - start_time)

Yukarıdaki betiği çalıştırdıktan sonra, aşağıda gösterildiği gibi her iki işlevin de yürütme zamanını alacağız.

Çıktı

Function A starts the execution:
Function A completes the execution:
0.0014599495514175942
Function B starts the execution
Function B completes the execution
0.0017024724827479076

Dekoratör işlevini kullanarak kendi zamanlayıcımızı yazmak

Python'da, kendi zamanlayıcımızı oluşturabiliriz, bu da tıpkı timeitmodül. Yardımı ile yapılabilirdecoratorişlevi. Aşağıda, özel zamanlayıcıya bir örnek verilmiştir -

import random
import time

def timer_func(func):

   def function_timer(*args, **kwargs):
   start = time.time()
   value = func(*args, **kwargs)
   end = time.time()
   runtime = end - start
   msg = "{func} took {time} seconds to complete its execution."
      print(msg.format(func = func.__name__,time = runtime))
   return value
   return function_timer

@timer_func
def Myfunction():
   for x in range(5):
   sleep_time = random.choice(range(1,3))
   time.sleep(sleep_time)

if __name__ == '__main__':
   Myfunction()

Yukarıdaki python betiği, rastgele zaman modüllerinin içe aktarılmasına yardımcı olur. Timer_func () dekoratör işlevini yarattık. Bunun içinde function_timer () işlevi vardır. Şimdi, yuvalanmış işlev, geçirilen işlevi çağırmadan önce zamanı alacaktır. Daha sonra fonksiyonun geri dönmesini bekler ve bitiş zamanını alır. Bu şekilde, nihayet python betiğinin yürütme zamanını yazdırmasını sağlayabiliriz. Komut dosyası, çıktıyı aşağıda gösterildiği gibi oluşturacaktır.

Çıktı

Myfunction took 8.000457763671875 seconds to complete its execution.

Profil oluşturma nedir?

Bazen programcı, o programın gerçek kapasitesini ölçmek için bellek kullanımı, zaman karmaşıklığı veya programlarla ilgili belirli talimatların kullanımı gibi bazı özellikleri ölçmek ister. Programla ilgili bu tür ölçümlere profilleme denir. Profil oluşturma, bu tür ölçümleri yapmak için dinamik program analizi kullanır.

Sonraki bölümlerde, Profil Oluşturma için farklı Python Modülleri hakkında bilgi edineceğiz.

cProfile - dahili modül

cProfileprofil oluşturma için yerleşik bir Python modülüdür. Modül, uzun süre çalışan programların profilini çıkarmak için uygun hale getiren makul ek yüke sahip bir C uzantısıdır. Çalıştırdıktan sonra, tüm işlevleri ve yürütme zamanlarını günlüğe kaydeder. Çok güçlüdür, ancak bazen yorumlanması ve harekete geçmesi biraz zordur. Aşağıdaki örnekte, aşağıdaki kodda cProfile kullanıyoruz -

Misal

def increment_global():

   global x
   x += 1

def taskofThread(lock):

   for _ in range(50000):
   lock.acquire()
   increment_global()
   lock.release()

def main():
   global x
   x = 0

   lock = threading.Lock()

   t1 = threading.Thread(target=taskofThread, args=(lock,))
   t2 = threading.Thread(target= taskofThread, args=(lock,))

   t1.start()
   t2.start()

   t1.join()
   t2.join()

if __name__ == "__main__":
   for i in range(5):
      main()
   print("x = {1} after Iteration {0}".format(i,x))

Yukarıdaki kod, thread_increment.pydosya. Şimdi, kodu komut satırında cProfile ile aşağıdaki gibi yürütün -

(base) D:\ProgramData>python -m cProfile thread_increment.py
x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4
      3577 function calls (3522 primitive calls) in 1.688 seconds

   Ordered by: standard name

   ncalls tottime percall cumtime percall filename:lineno(function)

   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:103(release)
   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:143(__init__)
   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:147(__enter__)
   … … … …

Yukarıdaki çıktıdan, cProfile'ın çağrılan tüm 3577 işlevi, her birinde harcanan süre ve kaç kez çağrıldıklarıyla birlikte yazdırdığı açıktır. Aşağıdakiler çıktıda aldığımız sütunlardır -

  • ncalls - Yapılan arama sayısıdır.

  • tottime - Verilen işlevde harcanan toplam süredir.

  • percall - Toplam zamanın ncalls ile bölünmesiyle elde edilen bölümü ifade eder.

  • cumtime- Bu ve tüm alt işlevlerde harcanan toplam süredir. Yinelemeli işlevler için bile doğrudur.

  • percall - Cumtime oranının ilkel çağrılara bölünmesiyle elde edilen orandır.

  • filename:lineno(function) - Temel olarak her bir işlevin ilgili verilerini sağlar.

Çok iş parçacıklı görevlerimiz için çok sayıda iş parçacığı oluşturmamız gerektiğini varsayalım. Çok fazla iş parçacığı nedeniyle birçok performans sorunu olabileceğinden, hesaplama açısından en pahalı olacaktır. Verimliliğin sınırlanması önemli bir sorun olabilir. Bu sorunu bir iş parçacığı havuzu oluşturarak çözebiliriz. Bir iş parçacığı havuzu, iş verilmeye hazır duran, önceden başlatılmış ve boşta olan evreler grubu olarak tanımlanabilir. İş parçacığı havuzu oluşturmak, çok sayıda görev yapmamız gerektiğinde her görev için yeni iş parçacığı başlatmaya tercih edilir. Bir iş parçacığı havuzu, çok sayıda iş parçacığının eşzamanlı yürütülmesini aşağıdaki gibi yönetebilir -

  • Bir evre havuzundaki bir evre yürütmeyi tamamlarsa, o evre yeniden kullanılabilir.

  • Bir evre sonlandırılırsa, o evreyi değiştirmek için başka bir evre oluşturulur.

Python Modülü - Concurrent.futures

Python standart kitaplığı şunları içerir: concurrent.futuresmodül. Bu modül, geliştiricilere eşzamansız görevleri başlatmak için yüksek düzeyde bir arayüz sağlamak için Python 3.2'ye eklenmiştir. İş parçacığı veya süreç havuzunu kullanarak görevleri yürütmek için arayüz sağlamak için Python'un iş parçacığı ve çoklu işlem modüllerinin üstündeki bir soyutlama katmanıdır.

Sonraki bölümlerimizde, concurrent.futures modülünün farklı sınıflarını öğreneceğiz.

Yürütme Sınıfı

Executorsoyut bir sınıftır concurrent.futuresPython modülü. Doğrudan kullanılamaz ve aşağıdaki beton alt sınıflardan birini kullanmamız gerekir -

  • ThreadPoolExecutor
  • ProcessPoolExecutor

ThreadPoolExecutor - Beton Alt Sınıfı

Executor sınıfının somut alt sınıflarından biridir. Alt sınıf çoklu iş parçacığı kullanır ve görevleri göndermek için bir iş parçacığı havuzu elde ederiz. Bu havuz, görevleri mevcut iş parçacıklarına atar ve çalıştırmaları için planlar.

ThreadPoolExecutor nasıl oluşturulur?

Yardımıyla concurrent.futures modül ve beton alt sınıfı Executor, kolayca bir iş parçacığı havuzu oluşturabiliriz. Bunun için bir yapmamız gerekiyorThreadPoolExecutorhavuzda istediğimiz konu sayısı ile. Varsayılan olarak sayı 5'tir. Daha sonra iş parçacığı havuzuna bir görev gönderebiliriz. Biz ne zamansubmit() bir görev, geri alıyoruz Future. Future nesnesinin adı verilen bir yöntemi vardırdone(), geleceğin çözülüp çözülmediğini söyler. Bununla, gelecekteki bu belirli nesne için bir değer belirlendi. Bir görev bittiğinde, iş parçacığı havuzu yürütücüsü değeri gelecekteki nesneye ayarlar.

Misal

from concurrent.futures import ThreadPoolExecutor
from time import sleep
def task(message):
   sleep(2)
   return message

def main():
   executor = ThreadPoolExecutor(5)
   future = executor.submit(task, ("Completed"))
   print(future.done())
   sleep(2)
   print(future.done())
   print(future.result())
if __name__ == '__main__':
main()

Çıktı

False
True
Completed

Yukarıdaki örnekte, bir ThreadPoolExecutor5 konu ile inşa edilmiştir. Ardından mesaj vermeden önce 2 saniye bekleyecek bir görev, iş parçacığı havuzu yürütücüsüne gönderilir. Çıktıdan görüldüğü gibi, görev 2 saniyeye kadar tamamlanmaz, bu nedenle ilk çağrıdone()False döndürür. 2 saniye sonra görev tamamlanır ve geleceğin sonucunu arayarak elde ederiz.result() yöntem.

ThreadPoolExecutor - Bağlam Yöneticisi

Örneklemenin başka bir yolu ThreadPoolExecutorbağlam yöneticisinin yardımıyla. Yukarıdaki örnekte kullanılan yönteme benzer şekilde çalışır. Bağlam yöneticisini kullanmanın temel avantajı, sözdizimsel olarak iyi görünmesidir. Örnekleme, aşağıdaki kod yardımıyla yapılabilir -

with ThreadPoolExecutor(max_workers = 5) as executor

Misal

Aşağıdaki örnek Python belgelerinden ödünç alınmıştır. Bu örnekte, her şeyden önceconcurrent.futuresmodülün içe aktarılması gerekiyor. Ardından adlı bir işlevload_url()istenen url'yi yükleyecek olan oluşturulur. İşlev daha sonra oluştururThreadPoolExecutorhavuzdaki 5 konu ile. ThreadPoolExecutorbağlam yöneticisi olarak kullanılmıştır. Geleceğin sonucunu arayarak alabiliriz.result() yöntem.

import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
   'http://www.cnn.com/',
   'http://europe.wsj.com/',
   'http://www.bbc.co.uk/',
   'http://some-made-up-domain.com/']

def load_url(url, timeout):
   with urllib.request.urlopen(url, timeout = timeout) as conn:
   return conn.read()

with concurrent.futures.ThreadPoolExecutor(max_workers = 5) as executor:

   future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
   for future in concurrent.futures.as_completed(future_to_url):
   url = future_to_url[future]
   try:
      data = future.result()
   except Exception as exc:
      print('%r generated an exception: %s' % (url, exc))
   else:
      print('%r page is %d bytes' % (url, len(data)))

Çıktı

Aşağıdaki Python betiğinin çıktısı olacaktır -

'http://some-made-up-domain.com/' generated an exception: <urlopen error [Errno 11004] getaddrinfo failed>
'http://www.foxnews.com/' page is 229313 bytes
'http://www.cnn.com/' page is 168933 bytes
'http://www.bbc.co.uk/' page is 283893 bytes
'http://europe.wsj.com/' page is 938109 bytes

Executor.map () işlevinin kullanımı

Python map()işlevi, bir dizi görevde yaygın olarak kullanılmaktadır. Böyle bir görev, yinelenebilir öğelerdeki her öğeye belirli bir işlevi uygulamaktır. Benzer şekilde, bir yineleyicinin tüm öğelerini bir işleve eşleyebilir ve bunları bağımsız işler olarak gönderebiliriz.ThreadPoolExecutor. İşlevin nasıl çalıştığını anlamak için aşağıdaki Python betiği örneğini düşünün.

Misal

Aşağıdaki bu örnekte, harita işlevi, square() değerler dizisindeki her değer için işlev.

from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import as_completed
values = [2,3,4,5]
def square(n):
   return n * n
def main():
   with ThreadPoolExecutor(max_workers = 3) as executor:
      results = executor.map(square, values)
for result in results:
      print(result)
if __name__ == '__main__':
   main()

Çıktı

Yukarıdaki Python betiği aşağıdaki çıktıyı üretir -

4
9
16
25

İşlem havuzu, iş parçacığı havuzunu oluşturduğumuz ve kullandığımız gibi oluşturulabilir ve kullanılabilir. Süreç havuzu, çalışmaya hazır duran, önceden başlatılmış ve atıl süreçler grubu olarak tanımlanabilir. İşlem havuzu oluşturmak, çok sayıda görev yapmamız gerektiğinde her görev için yeni süreçleri başlatmaya tercih edilir.

Python Modülü - Concurrent.futures

Python standart kitaplığı, concurrent.futures. Bu modül, geliştiricilere eşzamansız görevleri başlatmak için yüksek düzeyde bir arayüz sağlamak için Python 3.2'ye eklenmiştir. İş parçacığı veya süreç havuzunu kullanarak görevleri yürütmek için arayüz sağlamak için Python'un iş parçacığı ve çoklu işlem modüllerinin üstündeki bir soyutlama katmanıdır.

Sonraki bölümlerimizde, concurrent.futures modülünün farklı alt sınıflarına bakacağız.

Yürütme Sınıfı

Executor soyut bir sınıftır concurrent.futuresPython modülü. Doğrudan kullanılamaz ve aşağıdaki beton alt sınıflardan birini kullanmamız gerekir -

  • ThreadPoolExecutor
  • ProcessPoolExecutor

ProcessPoolExecutor - Somut bir alt sınıf

Executor sınıfının somut alt sınıflarından biridir. Çoklu işlemeyi kullanır ve görevleri göndermek için bir süreç havuzu elde ederiz. Bu havuz, görevleri mevcut işlemlere atar ve çalışacak şekilde planlar.

ProcessPoolExecutor nasıl oluşturulur?

Yardımıyla concurrent.futures modül ve beton alt sınıfı Executor, kolayca bir süreç havuzu oluşturabiliriz. Bunun için bir yapmamız gerekiyorProcessPoolExecutorhavuzda istediğimiz işlem sayısı ile. Varsayılan olarak sayı 5'tir. Bunun ardından işlem havuzuna bir görev gönderilir.

Misal

Şimdi iş parçacığı havuzu oluştururken kullandığımız örneği ele alacağız, tek fark şimdi kullanacağımız ProcessPoolExecutor onun yerine ThreadPoolExecutor .

from concurrent.futures import ProcessPoolExecutor
from time import sleep
def task(message):
   sleep(2)
   return message

def main():
   executor = ProcessPoolExecutor(5)
   future = executor.submit(task, ("Completed"))
   print(future.done())
   sleep(2)
   print(future.done())
   print(future.result())
if __name__ == '__main__':
main()

Çıktı

False
False
Completed

Yukarıdaki örnekte, bir İşlemPoolExecutor5 konu ile inşa edilmiştir. Ardından, mesaj vermeden önce 2 saniye bekleyecek bir görev, işlem havuzu yürütücüsüne gönderilir. Çıktıdan görüldüğü gibi, görev 2 saniyeye kadar tamamlanmaz, bu nedenle ilk çağrıdone()False döndürür. 2 saniye sonra görev tamamlanır ve geleceğin sonucunu arayarak elde ederiz.result() yöntem.

ProcessPoolExecutor Örnekleme - Bağlam Yöneticisi

ProcessPoolExecutor'u somutlaştırmanın başka bir yolu, bağlam yöneticisinin yardımıdır. Yukarıdaki örnekte kullanılan yönteme benzer şekilde çalışır. Bağlam yöneticisini kullanmanın temel avantajı, sözdizimsel olarak iyi görünmesidir. Örnekleme, aşağıdaki kod yardımıyla yapılabilir -

with ProcessPoolExecutor(max_workers = 5) as executor

Misal

Daha iyi anlamak için, iş parçacığı havuzu oluştururken kullanılan aynı örneği alıyoruz. Bu örnekte, şunu içe aktararak başlamalıyızconcurrent.futuresmodül. Ardından adlı bir işlevload_url()istenen url'yi yükleyecek olan oluşturulur. ProcessPoolExecutorhavuzdaki 5 iş parçacığı sayısı ile oluşturulur. SüreçPoolExecutorbağlam yöneticisi olarak kullanılmıştır. Geleceğin sonucunu arayarak alabiliriz.result() yöntem.

import concurrent.futures
from concurrent.futures import ProcessPoolExecutor
import urllib.request

URLS = ['http://www.foxnews.com/',
   'http://www.cnn.com/',
   'http://europe.wsj.com/',
   'http://www.bbc.co.uk/',
   'http://some-made-up-domain.com/']

def load_url(url, timeout):
   with urllib.request.urlopen(url, timeout = timeout) as conn:
      return conn.read()

def main():
   with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor:
      future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
      for future in concurrent.futures.as_completed(future_to_url):
      url = future_to_url[future]
      try:
         data = future.result()
      except Exception as exc:
         print('%r generated an exception: %s' % (url, exc))
      else:
         print('%r page is %d bytes' % (url, len(data)))

if __name__ == '__main__':
   main()

Çıktı

Yukarıdaki Python betiği aşağıdaki çıktıyı oluşturacaktır -

'http://some-made-up-domain.com/' generated an exception: <urlopen error [Errno 11004] getaddrinfo failed>
'http://www.foxnews.com/' page is 229476 bytes
'http://www.cnn.com/' page is 165323 bytes
'http://www.bbc.co.uk/' page is 284981 bytes
'http://europe.wsj.com/' page is 967575 bytes

Executor.map () işlevinin kullanımı

Python map()işlevi, bir dizi görevi gerçekleştirmek için yaygın olarak kullanılır. Böyle bir görev, yinelenebilir öğelerdeki her öğeye belirli bir işlevi uygulamaktır. Benzer şekilde, bir yineleyicinin tüm öğelerini bir işleve eşleyebilir ve bunları bağımsız işler olarakProcessPoolExecutor. Bunu anlamak için aşağıdaki Python komut dosyası örneğini düşünün.

Misal

Kullanarak iş parçacığı havuzu oluştururken kullandığımız örneği ele alacağız. Executor.map()işlevi. Aşağıda verilen örnekte, harita işlevisquare() değerler dizisindeki her değer için işlev.

from concurrent.futures import ProcessPoolExecutor
from concurrent.futures import as_completed
values = [2,3,4,5]
def square(n):
   return n * n
def main():
   with ProcessPoolExecutor(max_workers = 3) as executor:
      results = executor.map(square, values)
   for result in results:
      print(result)
if __name__ == '__main__':
   main()

Çıktı

Yukarıdaki Python betiği aşağıdaki çıktıyı oluşturacaktır

4
9
16
25

ProcessPoolExecutor ve ThreadPoolExecutor ne zaman kullanılır?

Artık hem Executor sınıfları - ThreadPoolExecutor ve ProcessPoolExecutor hakkında çalıştığımıza göre, hangi yürütücüyü ne zaman kullanacağımızı bilmemiz gerekiyor. CPU'ya bağlı iş yükleri durumunda ProcessPoolExecutor'u ve G / Ç'ye bağlı iş yükleri durumunda ThreadPoolExecutor'u seçmemiz gerekiyor.

Eğer kullanırsak ProcessPoolExecutor, o zaman GIL için endişelenmemize gerek kalmaz çünkü çoklu işlemeyi kullanır. Dahası, uygulama süresi ile karşılaştırıldığında daha az olacaktır.ThreadPoolExecution. Bunu anlamak için aşağıdaki Python komut dosyası örneğini düşünün.

Misal

import time
import concurrent.futures

value = [8000000, 7000000]

def counting(n):
   start = time.time()
   while n > 0:
      n -= 1
   return time.time() - start

def main():
   start = time.time()
   with concurrent.futures.ProcessPoolExecutor() as executor:
      for number, time_taken in zip(value, executor.map(counting, value)):
         print('Start: {} Time taken: {}'.format(number, time_taken))
   print('Total time taken: {}'.format(time.time() - start))

if __name__ == '__main__':
main()

Çıktı

Start: 8000000 Time taken: 1.5509998798370361
Start: 7000000 Time taken: 1.3259999752044678
Total time taken: 2.0840001106262207

Example- Python script with ThreadPoolExecutor:
import time
import concurrent.futures

value = [8000000, 7000000]

def counting(n):
   start = time.time()
   while n > 0:
      n -= 1
   return time.time() - start

def main():
   start = time.time()
   with concurrent.futures.ThreadPoolExecutor() as executor:
      for number, time_taken in zip(value, executor.map(counting, value)):
         print('Start: {} Time taken: {}'.format(number, time_taken))
      print('Total time taken: {}'.format(time.time() - start))

if __name__ == '__main__':
main()

Çıktı

Start: 8000000 Time taken: 3.8420000076293945
Start: 7000000 Time taken: 3.6010000705718994
Total time taken: 3.8480000495910645

Yukarıdaki her iki programın çıktılarından, kullanırken yürütme süresinin farkını görebiliriz ProcessPoolExecutor ve ThreadPoolExecutor.

Bu bölümde, çok işlemcili ve çok iş parçacıklı arasındaki karşılaştırmaya daha fazla odaklanacağız.

Çoklu işlem

Tek bir bilgisayar sistemi içinde iki veya daha fazla CPU biriminin kullanılmasıdır. Bilgisayar sistemimizde bulunan tam sayıda CPU çekirdeğini kullanarak donanımımızdan tam potansiyeli elde etmek için en iyi yaklaşım budur.

Çoklu kullanım

Bir CPU'nun aynı anda birden fazla iş parçacığı çalıştırarak işletim sistemi kullanımını yönetebilme yeteneğidir. Çoklu okumanın ana fikri, bir süreci birden çok iş parçacığına bölerek paralellik sağlamaktır.

Aşağıdaki tablo, aralarındaki bazı önemli farkları göstermektedir -

Çoklu işlem Çoklu programlama
Çoklu işlem, birden çok işlemin aynı anda birden çok CPU tarafından işlenmesini ifade eder. Çoklu programlama, birkaç programı aynı anda ana bellekte tutar ve bunları tek bir CPU kullanarak eşzamanlı olarak yürütür.
Birden çok CPU kullanır. Tek CPU kullanır.
Paralel işlemeye izin verir. Bağlam değiştirme gerçekleşir.
İşleri işlemek için daha az zaman harcanır. İşleri işlemek için daha fazla zaman harcanır.
Bilgisayar sistemindeki cihazların çok verimli kullanılmasını kolaylaştırır. Çoklu işlemden daha az verimli.
Genellikle daha pahalıdır. Bu tür sistemler daha ucuzdur.

Global tercüman kilidinin (GIL) etkisini ortadan kaldırma

Eşzamanlı uygulamalarla çalışırken, Python'da adı verilen bir sınırlama vardır. GIL (Global Interpreter Lock). GIL hiçbir zaman birden fazla CPU çekirdeği kullanmamıza izin vermez ve bu nedenle Python'da gerçek iş parçacığı olmadığını söyleyebiliriz. GIL, işleri güvenli hale getiren karşılıklı dışlama kilididir. Başka bir deyişle, GIL'in birden çok iş parçacığının Python kodunu paralel olarak çalıştırmasını engellediğini söyleyebiliriz. Kilit bir seferde yalnızca bir iş parçacığı tarafından tutulabilir ve bir iş parçacığı yürütmek istiyorsak önce kilidi almalıdır.

Çoklu işlemin kullanılmasıyla, GIL'in neden olduğu sınırlamayı etkili bir şekilde atlayabiliriz -

  • Çoklu işlemeyi kullanarak, birden çok işlemin yeteneğini kullanıyoruz ve dolayısıyla GIL'in birden çok örneğini kullanıyoruz.

  • Bu nedenle, herhangi bir zamanda programlarımızda bir iş parçacığının bayt kodunu çalıştırma kısıtlaması yoktur.

Python'da İşlemleri Başlatma

Çoklu işlem modülü içinde Python'da bir işlemi başlatmak için aşağıdaki üç yöntem kullanılabilir -

  • Fork
  • Spawn
  • Forkserver

Fork ile bir süreç oluşturmak

Çatal komutu, UNIX'te bulunan standart bir komuttur. Alt süreçler adı verilen yeni süreçler oluşturmak için kullanılır. Bu alt süreç, üst süreç adı verilen süreçle eşzamanlı olarak çalışır. Bu alt süreçler aynı zamanda üst süreçleriyle aynıdır ve ebeveynin kullanabileceği tüm kaynakları devralır. Fork ile bir işlem oluştururken aşağıdaki sistem çağrıları kullanılır -

  • fork()- Genellikle çekirdekte uygulanan bir sistem çağrısıdır. İşlemin bir kopyasını oluşturmak için kullanılır. P>

  • getpid() - Bu sistem çağrısı, çağıran işlemin işlem kimliğini (PID) döndürür.

Misal

Aşağıdaki Python komut dosyası örneği, yeni bir çocuk sürecin nasıl oluşturulacağını anlamanıza ve alt ve üst süreçlerin PID'lerini almanıza yardımcı olacaktır -

import os

def child():
   n = os.fork()
   
   if n > 0:
      print("PID of Parent process is : ", os.getpid())

   else:
      print("PID of Child process is : ", os.getpid())
child()

Çıktı

PID of Parent process is : 25989
PID of Child process is : 25990

Spawn ile bir süreç oluşturma

Spawn, yeni bir şeye başlamak demektir. Dolayısıyla, bir sürecin ortaya çıkması, bir ana süreç tarafından yeni bir sürecin oluşturulması anlamına gelir. Üst süreç, yürütülmesine eşzamansız olarak devam eder veya alt süreç yürütmeyi bitirene kadar bekler. Bir işlemi oluşturmak için şu adımları izleyin -

  • Çoklu işlem modülü içe aktarılıyor.

  • Nesne sürecini oluşturmak.

  • Süreç etkinliğini arayarak başlatmak start() yöntem.

  • İşlemin işini bitirmesini bekleyin ve arayarak çıkın join() yöntem.

Misal

Aşağıdaki Python komut dosyası örneği, üç işlemin üretilmesine yardımcı olur

import multiprocessing

def spawn_process(i):
   print ('This is process: %s' %i)
   return

if __name__ == '__main__':
   Process_jobs = []
   for i in range(3):
   p = multiprocessing.Process(target = spawn_process, args = (i,))
      Process_jobs.append(p)
   p.start()
   p.join()

Çıktı

This is process: 0
This is process: 1
This is process: 2

Forkserver ile bir süreç oluşturma

Forkserver mekanizması yalnızca dosya tanımlayıcılarının Unix Pipes üzerinden geçirilmesini destekleyen seçilmiş UNIX platformlarında mevcuttur. Forkserver mekanizmasının çalışmasını anlamak için aşağıdaki noktaları göz önünde bulundurun -

  • Yeni işlemi başlatmak için Forkserver mekanizması kullanılarak bir sunucu başlatılır.

  • Sunucu daha sonra komutu alır ve yeni işlemler oluşturmak için tüm istekleri işler.

  • Yeni bir süreç oluşturmak için python programımız Forkserver'a bir istek gönderecek ve bizim için bir süreç oluşturacaktır.

  • Nihayet bu yeni oluşturulan süreci programlarımızda kullanabiliriz.

Python'da Daemon işlemleri

Python multiprocessingmodül, arka plan programı seçeneği aracılığıyla daemon işlemlerine sahip olmamızı sağlar. Daemon süreçleri veya arka planda çalışan süreçler, arka planda çalışan iş parçacıklarıyla benzer konsepti izler. İşlemi arka planda yürütmek için, arka planda çalışan bayrağını true olarak ayarlamamız gerekir. Daemon süreci, ana işlem yürütüldüğü sürece çalışmaya devam edecek ve yürütülmesini tamamladıktan sonra veya ana programın sonlandırılmasından sonra sona erecektir.

Misal

Burada daemon evrelerinde kullanılanla aynı örneği kullanıyoruz. Tek fark, modülünmultithreading -e multiprocessingve daemonik bayrağı true olarak ayarlamak. Bununla birlikte, aşağıda gösterildiği gibi çıktıda bir değişiklik olacaktır -

import multiprocessing
import time

def nondaemonProcess():
   print("starting my Process")
   time.sleep(8)
   print("ending my Process")
def daemonProcess():
   while True:
   print("Hello")
   time.sleep(2)
if __name__ == '__main__':
   nondaemonProcess = multiprocessing.Process(target = nondaemonProcess)
   daemonProcess = multiprocessing.Process(target = daemonProcess)
   daemonProcess.daemon = True
   nondaemonProcess.daemon = False
   daemonProcess.start()
   nondaemonProcess.start()

Çıktı

starting my Process
ending my Process

Çıktı, arka plan programı evreleri tarafından üretilenle karşılaştırıldığında farklıdır, çünkü hiçbir arka plan programı kipindeki işlemin bir çıktısı yoktur. Bu nedenle, arka plan programı süreci, çalışan işlemlerin kalıcılığını önlemek için ana programlar sona erdikten sonra otomatik olarak sona erer.

Python'da süreçleri sonlandırma

Kullanarak bir işlemi hemen öldürebilir veya sonlandırabiliriz. terminate()yöntem. Bu yöntemi, işlevin yardımıyla oluşturulan çocuk süreci, yürütülmesini tamamlamadan hemen önce sonlandırmak için kullanacağız.

Misal

import multiprocessing
import time
def Child_process():
   print ('Starting function')
   time.sleep(5)
   print ('Finished function')
P = multiprocessing.Process(target = Child_process)
P.start()
print("My Process has terminated, terminating main thread")
print("Terminating Child Process")
P.terminate()
print("Child Process successfully terminated")

Çıktı

My Process has terminated, terminating main thread
Terminating Child Process
Child Process successfully terminated

Çıktı, programın Child_process () işlevi yardımıyla yaratılan çocuk sürecin yürütülmesinden önce sona erdiğini gösterir. Bu, alt sürecin başarıyla sonlandırıldığı anlamına gelir.

Python'da mevcut süreci tanımlama

İşletim sistemindeki her işlem, PID olarak bilinen işlem kimliğine sahiptir. Python'da, aşağıdaki komut yardımıyla mevcut sürecin PID'sini bulabiliriz -

import multiprocessing
print(multiprocessing.current_process().pid)

Misal

Aşağıdaki Python betiği örneği, ana işlemin PID'sini ve çocuk sürecin PID'sini bulmaya yardımcı olur -

import multiprocessing
import time
def Child_process():
   print("PID of Child Process is: {}".format(multiprocessing.current_process().pid))
print("PID of Main process is: {}".format(multiprocessing.current_process().pid))
P = multiprocessing.Process(target=Child_process)
P.start()
P.join()

Çıktı

PID of Main process is: 9401
PID of Child Process is: 9402

Alt sınıfta bir işlem kullanma

Konuları alt sınıflayarak oluşturabiliriz threading.Threadsınıf. Ek olarak, alt sınıflara ayırarak da süreçler oluşturabiliriz.multiprocessing.Processsınıf. Bir süreci alt sınıfta kullanmak için aşağıdaki noktaları dikkate almamız gerekir -

  • Yeni bir alt sınıf tanımlamamız gerekiyor. Process sınıf.

  • Geçersiz kılmalıyız _init_(self [,args] ) sınıf.

  • Geçersiz kılmalıyız run(self [,args] ) neyi uygulama yöntemi Process

  • Süreci çağırarak başlatmalıyızstart() yöntem.

Misal

import multiprocessing
class MyProcess(multiprocessing.Process):
   def run(self):
   print ('called run method in process: %s' %self.name)
   return
if __name__ == '__main__':
   jobs = []
   for i in range(5):
   P = MyProcess()
   jobs.append(P)
   P.start()
   P.join()

Çıktı

called run method in process: MyProcess-1
called run method in process: MyProcess-2
called run method in process: MyProcess-3
called run method in process: MyProcess-4
called run method in process: MyProcess-5

Python Çoklu İşlem Modülü - Havuz Sınıfı

Basit paralelden bahsedersek processingPython uygulamalarımızdaki görevler, ardından çoklu işlem modülü bize Pool sınıfını sağlar. Aşağıdaki yöntemlerPool sınıf, ana programımızdaki alt süreçlerin sayısını artırmak için kullanılabilir

apply () yöntemi

Bu yöntem,.submit()yöntemi .ThreadPoolExecutor.Sonuç hazır olana kadar bloke eder.

apply_async () yöntemi

Görevlerimizin paralel yürütülmesine ihtiyaç duyduğumuzda,apply_async()havuza görev gönderme yöntemi. Tüm alt işlemler yürütülene kadar ana iş parçacığını kilitlemeyecek zaman uyumsuz bir işlemdir.

map () yöntemi

Tıpkı apply()yöntem, sonuç hazır olana kadar da bloke eder. Yerleşik olana eşdeğerdirmap() yinelenebilir verileri birkaç parçaya bölen ve işlem havuzuna ayrı görevler olarak gönderen işlev.

map_async () yöntemi

Bu bir varyantıdır map() yöntem olarak apply_async() için apply()yöntem. Bir sonuç nesnesi döndürür. Sonuç hazır olduğunda, ona bir çağrılabilir uygulanır. Çağrılabilir hemen tamamlanmalıdır; aksi takdirde, sonuçları işleyen iş parçacığı engellenecektir.

Misal

Aşağıdaki örnek, paralel yürütme gerçekleştirmek için bir işlem havuzu uygulamanıza yardımcı olacaktır. Basit bir sayının karesi hesaplaması,square() aracılığıyla işlev multiprocessing.Poolyöntem. Sonrapool.map() 5'i göndermek için kullanılmıştır, çünkü girdi, 0'dan 4'e kadar olan tamsayıların bir listesidir. Sonuç, p_outputs ve basılır.

def square(n):
   result = n*n
   return result
if __name__ == '__main__':
   inputs = list(range(5))
   p = multiprocessing.Pool(processes = 4)
   p_outputs = pool.map(function_square, inputs)
   p.close()
   p.join()
   print ('Pool :', p_outputs)

Çıktı

Pool : [0, 1, 4, 9, 16]

Süreç içi iletişim, süreçler arasında veri alışverişi anlamına gelir. Paralel uygulamanın geliştirilmesi için süreçler arasında veri alışverişi yapmak gerekir. Aşağıdaki şema, birden çok alt süreç arasında senkronizasyon için çeşitli iletişim mekanizmalarını göstermektedir -

Çeşitli İletişim Mekanizmaları

Bu bölümde, çeşitli iletişim mekanizmaları hakkında bilgi edineceğiz. Mekanizmalar aşağıda açıklanmıştır -

Kuyruklar

Kuyruklar, çok işlemli programlarla kullanılabilir. Queue sınıfımultiprocessing modül benzer Queue.Queuesınıf. Dolayısıyla aynı API kullanılabilir.Multiprocessing.Queue bize süreçler arasında iş parçacığı ve işlem güvenli FIFO (ilk giren ilk çıkar) iletişim mekanizması sağlar.

Misal

Aşağıda, çoklu işlemin Queue sınıfı kavramını anlamak için python resmi belgelerinden çoklu işlemeyle ilgili basit bir örnek verilmiştir.

from multiprocessing import Process, Queue
import queue
import random
def f(q):
   q.put([42, None, 'hello'])
def main():
   q = Queue()
   p = Process(target = f, args = (q,))
   p.start()
   print (q.get())
if __name__ == '__main__':
   main()

Çıktı

[42, None, 'hello']

Borular

Çok işlemli programlarda süreçler arasında iletişim kurmak için kullanılan bir veri yapısıdır. Boru () işlevi, varsayılan olarak çift yönlü (iki yönlü) olan bir boru ile bağlanmış bir çift bağlantı nesnesi döndürür. Aşağıdaki şekilde çalışır -

  • Borunun iki ucunu temsil eden bir çift bağlantı nesnesi döndürür.

  • Her nesnenin iki yöntemi vardır - send() ve recv()süreçler arasında iletişim kurmak için.

Misal

Aşağıdaki, python resmi belgelerinden alınan çoklu işlem kavramını anlamak için basit bir örnektir. Pipe() çoklu işlemenin işlevi.

from multiprocessing import Process, Pipe

def f(conn):
   conn.send([42, None, 'hello'])
   conn.close()

if __name__ == '__main__':
   parent_conn, child_conn = Pipe()
   p = Process(target = f, args = (child_conn,))
   p.start()
   print (parent_conn.recv())
   p.join()

Çıktı

[42, None, 'hello']

Yönetici

Yönetici, tüm kullanıcıları arasında paylaşılan bilgileri koordine etmenin bir yolunu sağlayan bir çoklu işlem modülü sınıfıdır. Yönetici nesnesi, paylaşılan nesneleri yöneten ve diğer işlemlerin bunları değiştirmesine izin veren bir sunucu işlemini kontrol eder. Başka bir deyişle, yöneticiler, farklı süreçler arasında paylaşılabilen veriler oluşturmanın bir yolunu sağlar. Yönetici nesnesinin farklı özellikleri aşağıdadır -

  • Yöneticinin temel özelliği, paylaşılan nesneleri yöneten bir sunucu sürecini kontrol etmektir.

  • Bir diğer önemli özellik, herhangi bir işlem değiştirdiğinde tüm paylaşılan nesneleri güncellemektir.

Misal

Aşağıda, sunucu işleminde bir liste kaydı oluşturmak ve ardından bu listeye yeni bir kayıt eklemek için yönetici nesnesini kullanan bir örnek verilmiştir.

import multiprocessing

def print_records(records):
   for record in records:
      print("Name: {0}\nScore: {1}\n".format(record[0], record[1]))

def insert_record(record, records):
   records.append(record)
      print("A New record is added\n")

if __name__ == '__main__':
   with multiprocessing.Manager() as manager:

      records = manager.list([('Computers', 1), ('Histoty', 5), ('Hindi',9)])
      new_record = ('English', 3)

      p1 = multiprocessing.Process(target = insert_record, args = (new_record, records))
      p2 = multiprocessing.Process(target = print_records, args = (records,))
	  p1.start()
      p1.join()
      p2.start()
      p2.join()

Çıktı

A New record is added

Name: Computers
Score: 1

Name: Histoty
Score: 5

Name: Hindi
Score: 9

Name: English
Score: 3

Yöneticide Ad Alanları Kavramı

Yönetici Sınıfı, birden çok işlemde çeşitli öznitelikleri paylaşmanın hızlı bir yolu olan ad alanları kavramıyla birlikte gelir. Ad alanları, çağrılabilen, ancak yazılabilir niteliklere sahip genel bir yöntem içermez.

Misal

Aşağıdaki Python komut dosyası örneği, ana süreç ve alt süreç boyunca veri paylaşmak için ad alanlarını kullanmamıza yardımcı olur -

import multiprocessing

def Mng_NaSp(using_ns):

   using_ns.x +=5
   using_ns.y *= 10

if __name__ == '__main__':
   manager = multiprocessing.Manager()
   using_ns = manager.Namespace()
   using_ns.x = 1
   using_ns.y = 1

   print ('before', using_ns)
   p = multiprocessing.Process(target = Mng_NaSp, args = (using_ns,))
   p.start()
   p.join()
   print ('after', using_ns)

Çıktı

before Namespace(x = 1, y = 1)
after Namespace(x = 6, y = 10)

Ctypes-Array ve Değer

Çoklu işlem modülü, verileri paylaşılan bir bellek haritasında depolamak için Dizi ve Değer nesneleri sağlar. Array paylaşılan bellekten ayrılmış bir ctypes dizisidir ve Value paylaşılan bellekten ayrılmış bir ctypes nesnesidir.

Çoklu işlemden İşlem, Değer, Dizi ile birlikte olmak.

Misal

Aşağıdaki Python betiği, işlemler arasında bazı verileri paylaşmak için Ctypes Array ve Value kullanmak için python belgelerinden alınan bir örnektir.

def f(n, a):
   n.value = 3.1415927
   for i in range(len(a)):
   a[i] = -a[i]

if __name__ == '__main__':
   num = Value('d', 0.0)
   arr = Array('i', range(10))

   p = Process(target = f, args = (num, arr))
   p.start()
   p.join()
   print (num.value)
   print (arr[:])

Çıktı

3.1415927
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]

Sıralı Süreçleri İletme (CSP)

CSP, sistemlerin eşzamanlı modeller içeren diğer sistemlerle etkileşimini göstermek için kullanılır. CSP, mesaj geçişi yoluyla eşzamanlı veya program yazmak için bir çerçevedir ve bu nedenle eşzamanlılığı tanımlamak için etkilidir.

Python kitaplığı - PyCSP

CSP'de bulunan temel ilkelleri uygulamak için Python, PyCSP adlı bir kitaplığa sahiptir. Çok kolay anlaşılabilmesi için uygulamayı çok kısa ve okunaklı tutar. PyCSP'nin temel işlem ağı aşağıdadır -

Yukarıdaki PyCSP işlem ağında, iki süreç vardır - İşlem1 ve İşlem 2. Bu işlemler, mesajları iki kanaldan - kanal 1 ve kanal 2'den geçirerek iletişim kurar.

PyCSP'yi Kurmak

Aşağıdaki komutun yardımıyla Python kitaplığı PyCSP'yi kurabiliriz -

pip install PyCSP

Misal

Python betiğini takip etmek, iki işlemi paralel olarak çalıştırmak için basit bir örnektir. PyCSP python kütüphanesinin yardımı ile yapılır -

from pycsp.parallel import *
import time
@process
def P1():
   time.sleep(1)
   print('P1 exiting')
@process
def P2():
   time.sleep(1)
   print('P2 exiting')
def main():
   Parallel(P1(), P2())
   print('Terminating')
if __name__ == '__main__':
   main()

Yukarıdaki komut dosyasında iki işlev, yani P1 ve P2 oluşturuldu ve sonra süslendi @process bunları süreçlere dönüştürmek için.

Çıktı

P2 exiting
P1 exiting
Terminating

Olay odaklı programlama olaylara odaklanır. Sonunda, programın akışı olaylara bağlıdır. Şimdiye kadar, sıralı veya paralel yürütme modeliyle uğraşıyorduk, ancak olay güdümlü programlama konseptine sahip modele asenkron model denir. Olay güdümlü programlama, her zaman yeni gelen olayları dinleyen bir olay döngüsüne bağlıdır. Olay güdümlü programlamanın çalışması olaylara bağlıdır. Bir olay bir kez döndüğünde, olaylar neyin hangi sırayla yürütüleceğine karar verir. Aşağıdaki akış şeması, bunun nasıl çalıştığını anlamanıza yardımcı olacaktır -

Python Modülü - Asyncio

Asyncio modülü Python 3.4'e eklenmiştir ve ortak rutinler kullanarak tek iş parçacıklı eşzamanlı kod yazmak için altyapı sağlar. Asyncio modülü tarafından kullanılan farklı kavramlar aşağıdadır -

Olay döngüsü

Olay döngüsü, bir hesaplama kodundaki tüm olayları işleyen bir işlevdir. Tüm programın yürütülmesi sırasında yol boyunca hareket eder ve olayların gelen ve yürütülmesini takip eder. Asyncio modülü, işlem başına tek bir olay döngüsüne izin verir. Bir olay döngüsünü yönetmek için Asyncio modülü tarafından sağlanan bazı yöntemler şunlardır -

  • loop = get_event_loop() - Bu yöntem, geçerli bağlam için olay döngüsünü sağlayacaktır.

  • loop.call_later(time_delay,callback,argument) - Bu yöntem, verilen time_delay saniyeden sonra çağrılacak geri aramayı düzenler.

  • loop.call_soon(callback,argument)- Bu yöntem, mümkün olan en kısa sürede çağrılacak bir geri aramayı düzenler. Call_soon () döndükten sonra ve denetim olay döngüsüne döndüğünde geri çağrı çağrılır.

  • loop.time() - Bu yöntem, olay döngüsünün dahili saatine göre geçerli zamanı döndürmek için kullanılır.

  • asyncio.set_event_loop() - Bu yöntem, geçerli bağlam için olay döngüsünü döngüye ayarlayacaktır.

  • asyncio.new_event_loop() - Bu yöntem, yeni bir olay döngüsü nesnesi oluşturur ve döndürür.

  • loop.run_forever() - Bu yöntem stop () yöntemi çağrılana kadar çalışacaktır.

Misal

Aşağıdaki olay döngüsü örneği yazdırmaya yardımcı olur hello worldget_event_loop () yöntemini kullanarak. Bu örnek Python resmi belgelerinden alınmıştır.

import asyncio

def hello_world(loop):
   print('Hello World')
   loop.stop()

loop = asyncio.get_event_loop()

loop.call_soon(hello_world, loop)

loop.run_forever()
loop.close()

Çıktı

Hello World

Vadeli işlemler

Bu, gerçekleştirilmemiş bir hesaplamayı temsil eden concurrent.futures.Future sınıfıyla uyumludur. Asyncio.futures.Future ve concurrent.futures arasında aşağıdaki farklılıklar vardır.

  • result () ve exception () yöntemleri bir zaman aşımı argümanı almaz ve gelecek henüz tamamlanmadığında bir istisna yaratmaz.

  • Add_done_callback () ile kaydedilen geri aramalar her zaman olay döngüsünün call_soon () aracılığıyla çağrılır.

  • asyncio.futures.Future sınıfı, concurrent.futures paketindeki wait () ve as_completed () işlevleriyle uyumlu değildir.

Misal

Aşağıdaki, asyncio.futures.future sınıfını nasıl kullanacağınızı anlamanıza yardımcı olacak bir örnektir.

import asyncio

async def Myoperation(future):
   await asyncio.sleep(2)
   future.set_result('Future Completed')

loop = asyncio.get_event_loop()
future = asyncio.Future()
asyncio.ensure_future(Myoperation(future))
try:
   loop.run_until_complete(future)
   print(future.result())
finally:
   loop.close()

Çıktı

Future Completed

Coroutines

Asyncio'daki coroutines kavramı, diş çekme modülü altındaki standart Thread nesnesi konseptine benzer. Bu, altyordam kavramının genellemesidir. Bir koroutin, harici işlemeyi bekler ve harici işleme tamamlandığında durduğu noktadan geri dönmesi için yürütme sırasında askıya alınabilir. Aşağıdaki iki yol, eş anlamlıları uygulamamıza yardımcı olur -

eşzamansız def işlevi ()

Bu, Asyncio modülü altında coroutinlerin gerçeklenmesi için bir yöntemdir. Aşağıdakiler aynı için bir Python betiğidir -

import asyncio

async def Myoperation():
   print("First Coroutine")

loop = asyncio.get_event_loop()
try:
   loop.run_until_complete(Myoperation())

finally:
   loop.close()

Çıktı

First Coroutine

@ asyncio.coroutine dekoratör

Eşgüdümlerin gerçeklenmesi için başka bir yöntem, oluşturuculardan @ asyncio.coroutine dekoratörüyle birlikte kullanılmasıdır. Aşağıdakiler aynı için bir Python betiğidir -

import asyncio

@asyncio.coroutine
def Myoperation():
   print("First Coroutine")

loop = asyncio.get_event_loop()
try:
   loop.run_until_complete(Myoperation())

finally:
   loop.close()

Çıktı

First Coroutine

Görevler

Asyncio modülünün bu alt sınıfı, bir olay döngüsü içinde paralel bir şekilde eşgüdümlerin yürütülmesinden sorumludur. Aşağıdaki Python betiği, bazı görevleri paralel olarak işlemenin bir örneğidir.

import asyncio
import time
async def Task_ex(n):
   time.sleep(1)
   print("Processing {}".format(n))
async def Generator_task():
   for i in range(10):
      asyncio.ensure_future(Task_ex(i))
   int("Tasks Completed")
   asyncio.sleep(2)

loop = asyncio.get_event_loop()
loop.run_until_complete(Generator_task())
loop.close()

Çıktı

Tasks Completed
Processing 0
Processing 1
Processing 2
Processing 3
Processing 4
Processing 5
Processing 6
Processing 7
Processing 8
Processing 9

Taşımalar

Asyncio modülü, çeşitli iletişim türlerini uygulamak için taşıma sınıfları sağlar. Bu sınıflar iş parçacığı açısından güvenli değildir ve iletişim kanalı oluşturulduktan sonra her zaman bir protokol örneğiyle eşleştirilir.

Aşağıdakiler, BaseTransport'tan miras alınan farklı taşıma türleridir -

  • ReadTransport - Bu, salt okunur taşımalar için bir arayüzdür.

  • WriteTransport - Bu, salt yazılabilir taşımalar için bir arayüzdür.

  • DatagramTransport - Bu, verileri göndermek için bir arayüzdür.

  • BaseSubprocessTransport - BaseTransport sınıfına benzer.

Aşağıda, dört aktarım türü arasında sonradan geçici olan beş farklı BaseTransport sınıfı yöntemi verilmiştir:

  • close() - Taşıyıcıyı kapatır.

  • is_closing() - Taşıma kapanıyorsa veya zaten kapalıysa, bu yöntem true döndürür.

  • get_extra_info(name, default = none) - Bu bize ulaşımla ilgili bazı ekstra bilgiler verecektir.

  • get_protocol() - Bu yöntem mevcut protokolü döndürecektir.

Protokoller

Asyncio modülü, ağ protokollerinizi uygulamak için alt sınıflara ayırabileceğiniz temel sınıflar sağlar. Bu sınıflar nakliye ile bağlantılı olarak kullanılır; protokol, gelen verileri ayrıştırır ve giden verilerin yazılmasını isterken, taşıma gerçek G / Ç ve arabelleğe alma işleminden sorumludur. Aşağıda üç Protokol sınıfı bulunmaktadır -

  • Protocol - Bu, TCP ve SSL aktarımlarıyla kullanım için akış protokollerini uygulamak için temel sınıftır.

  • DatagramProtocol - Bu, UDP aktarımlarıyla kullanım için veri birimi protokollerini uygulamak için temel sınıftır.

  • SubprocessProtocol - Bu, bir dizi tek yönlü kanal aracılığıyla alt süreçlerle iletişim kuran protokolleri uygulamak için temel sınıftır.

Reaktif programlama, veri akışları ve değişimin yayılmasıyla ilgilenen bir programlama paradigmasıdır. Bu, bir bileşen tarafından bir veri akışı yayınlandığında, değişikliğin reaktif programlama kitaplığı tarafından diğer bileşenlere yayılacağı anlamına gelir. Değişimin yayılması, nihai alıcıya ulaşana kadar devam edecektir. Olay güdümlü ve reaktif programlama arasındaki fark, olay güdümlü programlamanın olaylar etrafında dönmesi ve reaktif programlamanın veriler etrafında dönmesidir.

Reaktif programlama için ReactiveX veya RX

ReactiveX veya Raective Extension, reaktif programlamanın en ünlü uygulamasıdır. ReactiveX'in çalışması aşağıdaki iki sınıfa bağlıdır -

Gözlenebilir sınıf

Bu sınıf, veri akışı veya olayların kaynağıdır ve gelen verileri paketler, böylece veriler bir iş parçacığından diğerine aktarılabilir. Bazı gözlemciler abone olana kadar veri vermeyecektir.

Gözlemci sınıfı

Bu sınıf, tarafından yayılan veri akışını tüketir observable. Gözlemlenebilir olan birden fazla gözlemci olabilir ve her gözlemci yayılan her veri öğesini alacaktır. Gözlemci, gözlemlenebilir olana abone olarak üç tür olay alabilir -

  • on_next() event - Veri akışında bir öğe olduğu anlamına gelir.

  • on_completed() event - Emisyonun sona erdiğini ve başka ürün gelmeyeceğini gösterir.

  • on_error() event - Aynı zamanda emisyonun sona ermesi anlamına gelir, ancak bir hata atıldığında observable.

RxPY - Reaktif Programlama için Python Modülü

RxPY, reaktif programlama için kullanılabilen bir Python modülüdür. Modülün kurulu olduğundan emin olmamız gerekiyor. RxPY modülünü kurmak için aşağıdaki komut kullanılabilir -

pip install RxPY

Misal

Aşağıdakiler bir Python betiğidir. RxPY modül ve sınıfları Observable ve Observe forreaktif programlama. Temelde iki sınıf vardır -

  • get_strings() - dizeleri gözlemciden almak için.

  • PrintObserver()- gözlemciden dizeleri yazdırmak için. Gözlemci sınıfının üç olayını da kullanır. Ayrıca subscribe () sınıfını kullanır.

from rx import Observable, Observer
def get_strings(observer):
   observer.on_next("Ram")
   observer.on_next("Mohan")
   observer.on_next("Shyam")
      observer.on_completed()
class PrintObserver(Observer):
   def on_next(self, value):
      print("Received {0}".format(value))
   def on_completed(self):
   print("Finished")
   def on_error(self, error):
      print("Error: {0}".format(error))
source = Observable.create(get_strings)
source.subscribe(PrintObserver())

Çıktı

Received Ram
Received Mohan
Received Shyam
Finished

Reaktif programlama için PyFunctional kitaplığı

PyFunctionalreaktif programlama için kullanılabilecek başka bir Python kitaplığıdır. Python programlama dilini kullanarak işlevsel programlar oluşturmamızı sağlar. Kullanışlı, çünkü zincirleme işlevsel operatörler kullanarak veri hatları oluşturmamıza izin veriyor.

RxPY ve PyFunctional arasındaki fark

Her iki kitaplık da reaktif programlama için kullanılır ve akışı benzer şekilde işler, ancak ikisi arasındaki temel fark verilerin işlenmesine bağlıdır. RxPY sistemdeki verileri ve olayları yönetirken PyFunctional işlevsel programlama paradigmalarını kullanarak verilerin dönüştürülmesine odaklanmıştır.

PyFunctional Modülünü Kurmak

Kullanmadan önce bu modülü kurmamız gerekiyor. Aşağıdaki gibi pip komutunun yardımıyla kurulabilir -

pip install pyfunctional

Misal

Aşağıdaki örnek kullanımlar the PyFunctional modülü ve onun seqYineleyebileceğimiz ve işleyebileceğimiz akış nesnesi olarak hareket eden sınıf. Bu programda, her değeri ikiye katlayan lamda işlevini kullanarak diziyi eşler, ardından x'in 4'ten büyük olduğu değeri filtreler ve son olarak diziyi kalan tüm değerlerin toplamına indirger.

from functional import seq

result = seq(1,2,3).map(lambda x: x*2).filter(lambda x: x > 4).reduce(lambda x, y: x + y)

print ("Result: {}".format(result))

Çıktı

Result: 6