Çoklu iş parçacığı Python Örnek ile: GIL'yi öğrenin Python
⚡ Akıllı Özet
Çoklu iş parçacığı Python Bir işlem içinde birden fazla iş parçacığı çalıştırarak bellek paylaşımını ve eş zamanlı çalışmayı sağlar. İş parçacığı modülü bu iş parçacıklarını oluşturur ve yönetir; Global Interpreter Lock ise gerçek paralelliği sınırlandırır, bu da tekniği girdi/çıktı odaklı görevler için en uygun hale getirir.

MKS Python programlama dili çoklu işlem veya çoklu iş parçacığı kullanmanıza olanak tanır. Bu eğitimde, çok iş parçacıklı uygulamaların nasıl yazılacağını öğreneceksiniz. Python.
Konu Nedir?
Eşzamanlı programlamada iş parçacığı (thread), bir yürütme birimidir. Çoklu iş parçacığı (multithreading), bir CPU'nun aynı anda tek bir işlemin birçok görevini yürütmesine olanak tanıyan bir tekniktir. Bu iş parçacıkları, işlem kaynaklarını paylaşırken ayrı ayrı yürütülebilir.
Süreç Nedir?
Bir işlem temelde yürütülen programdır. Bilgisayarınızda bir uygulama (örneğin bir tarayıcı veya metin düzenleyici) başlattığınızda, işletim sistemi bir işlem oluşturur. proses.
Çoklu iş parçacığı nedir? Python?
Çoklu iş parçacığı Python Çoklu iş parçacığı programlama, bir işlemdeki birden fazla iş parçacığının veri alanını ana iş parçacığıyla paylaştığı, bu sayede iş parçacıkları arasında bilgi paylaşımını ve iletişimi kolay ve verimli hale getiren bilinen bir tekniktir. İş parçacıkları işlemlere göre daha hafiftir. Birden fazla iş parçacığı, işlem kaynaklarını paylaşırken ayrı ayrı çalışabilir. Çoklu iş parçacığının amacı, aynı anda birden fazla görevi ve işlevi çalıştırmaktır.
Çoklu İşleme Nedir?
Çoklu İşlem birden fazla ilgisiz işlemi aynı anda çalıştırmanıza olanak tanır. Bu işlemler kaynaklarını paylaşmaz ve IPC aracılığıyla iletişim kurar.
Python Çoklu iş parçacığı vs Çoklu işlem
Süreçleri ve iş parçacıklarını anlamak için şu senaryoyu düşünün: Bilgisayarınızdaki bir .exe dosyası bir programdır. Onu açtığınızda, işletim sistemi onu belleğe yükler ve işlemci onu çalıştırır. Şu anda çalışan program örneğine süreç denir.
Her sürecin iki temel bileşeni vardır:
- MKS Code
- Veri
Artık bir süreç, adı verilen bir veya daha fazla alt parça içerebilir. İş Parçacığı. Bu, işletim sisteminin mimarisine bağlıdır. Bir iş parçacığını, işletim sistemi tarafından ayrı olarak yürütülebilen bir işlem bölümü olarak düşünebilirsiniz.
Başka bir deyişle, işletim sistemi tarafından bağımsız olarak çalıştırılabilen bir dizi talimattır. Tek bir işlem içindeki iş parçacıkları, o işlemin verilerini paylaşır ve paralelliği kolaylaştırmak için birlikte çalışacak şekilde tasarlanmıştır.
Neden Çoklu İş Parçacığı kullanmalı?
Çoklu iş parçacığı, bir uygulamayı birden fazla alt göreve bölmenize ve bu görevleri aynı anda çalıştırmanıza olanak tanır. Çoklu iş parçacığını doğru şekilde kullanırsanız, uygulama hızınız, performansınız ve işlemeniz iyileştirilebilir.
Python Çok iş parçacığı
Python Hem çoklu işlem hem de çoklu iş parçacığı için yapıları destekler. Bu eğitimde öncelikle bunların uygulanmasına odaklanacaksınız. okuyuculu olan uygulamalar Pythonİki ana modül, iş parçacıklarını yönetmek için kullanılabilir. Python:
- MKS iplik modül ve
- MKS threading modül
Ancak PythonAyrıca küresel yorumlayıcı kilidi (GIL) adı verilen bir şey de var. Bu, performansta önemli bir artış sağlamaz ve hatta performans sorunlarına yol açabilir. azaltmak bazı çok iş parçacıklı uygulamaların performansı. Bu eğitimin gelecek bölümlerinde bununla ilgili her şeyi öğreneceksiniz.
Thread ve Threading modülleri
Bu eğitimde öğreneceğiniz iki modül şunlardır: iplik modülü ve iş parçacığı modülü.
Ancak iş parçacığı modülü uzun süredir kullanımdan kaldırılmıştır. Şununla başlıyor: Python 3, eski olarak belirlenmiş ve yalnızca şu şekilde erişilebilir: _iplik geriye dönük uyumluluk için.
Daha üst seviyeyi kullanmalısınız threading Dağıtmayı planladığınız uygulamalar için modül. İş parçacığı modülü burada sadece eğitim amaçlı ele alınmıştır.
Konu Modülü
Bu modülü kullanarak yeni bir iş parçacığı oluşturmanın sözdizimi aşağıdaki gibidir:
thread.start_new_thread(function_name, arguments)
Tamam, artık kodlamaya başlamanın temel teorisini öğrendiniz. Öyleyse aç IDLE veya bir not defterine yazın ve şunları yazın:
import time import _thread def thread_test(name, wait): i = 0 while i <= 3: time.sleep(wait) print("Running %s\n" %name) i = i + 1 print("%s has finished execution" %name) if __name__ == "__main__": _thread.start_new_thread(thread_test, ("First Thread", 1)) _thread.start_new_thread(thread_test, ("Second Thread", 2)) _thread.start_new_thread(thread_test, ("Third Thread", 3))
Dosyayı kaydedin ve programı çalıştırmak için F5 tuşuna basın. Her şey doğru yapıldıysa görmeniz gereken çıktı şudur:
İlerleyen bölümlerde yarış koşulları ve bunlarla nasıl başa çıkılacağı hakkında daha fazla bilgi edineceksiniz.
KOD AÇIKLAMA
- Bu ifadeler, yürütme ve geciktirme işlemlerini yönetmek için kullanılan zaman ve iş parçacığı modüllerini içe aktarır. Python İş Parçacığı.
- Burada, adı verilen bir işlevi tanımladınız. thread_test, tarafından çağrılacak start_new_thread Bu metot, dört yineleme boyunca bir while döngüsü çalıştırır ve onu çağıran iş parçacığının adını yazdırır. Yineleme tamamlandığında, iş parçacığının yürütmeyi bitirdiğini belirten bir mesaj yazdırır.
- Bu programınızın ana bölümüdür. Burada aramanız yeterli start_new_thread ile yöntem iş parçacığı_testi Fonksiyonu argüman olarak geçirin. Bu, argüman olarak geçirdiğiniz fonksiyon için yeni bir iş parçacığı oluşturacak ve onu çalıştırmaya başlayacaktır. (thread_test) ifadesini, iş parçacığı olarak çalıştırmak istediğiniz başka herhangi bir fonksiyonla değiştirebileceğinizi unutmayın.
Diş Açma Modülü
Bu modül, iş parçacığının üst düzey uygulamasıdır Python ve çok iş parçacıklı uygulamaları yönetmek için fiili standart. İplik modülüyle karşılaştırıldığında geniş bir özellik yelpazesi sunar.
Diş Açma modülünün yapısı
Bu modülde tanımlanan bazı yararlı işlevlerin listesi aşağıda verilmiştir:
| Fonksiyon adı | Açıklama |
|---|---|
| aktifSayı() | Sayısını döndürür Konu Hâlâ hayatta olan nesneler. |
| currentThread() | Thread sınıfının geçerli nesnesini döndürür. |
| numaralandırmak() | Tüm etkin Thread nesnelerini listeler. |
| isDaemon() | İş parçacığı bir daemon ise true değerini döndürür. |
| yaşıyor() | İş parçacığı hala hayattaysa true değerini döndürür. |
| Konu Sınıfı yöntemleri | |
| Başlat() | Bir iş parçacığının etkinliğini başlatır. Her iş parçacığı için yalnızca bir kez çağrılmalıdır çünkü birden çok kez çağrılırsa çalışma zamanı hatası verir. |
| Çalıştırmak() | Bu yöntem bir iş parçacığının etkinliğini belirtir ve İş Parçacığı sınıfını genişleten bir sınıf tarafından geçersiz kılınabilir. |
| katılmak() | join() yönteminin çağrıldığı iş parçacığı sonlandırılana kadar diğer kodun yürütülmesini engeller. |
Arka Plan Hikayesi: Konu Sınıfı
Threading modülünü kullanarak çoklu iş parçacıklı programlar kodlamaya başlamadan önce, Thread sınıfını anlamak çok önemlidir. Thread sınıfı, bir iş parçacığının şablonunu ve işlemlerini tanımlayan temel sınıftır. Python.
Çok iş parçacıklı bir oluşturmanın en yaygın yolu Python Uygulamanın amacı, Thread sınıfını genişleten ve run() yöntemini geçersiz kılan bir sınıf tanımlamaktır.
Thread sınıfı özetle ayrı bir kod dizisinde çalışan bir kod dizisini ifade eder. iplik kontrol
Yani, çok iş parçacıklı bir uygulama yazarken şunları yapacaksınız:
- Thread sınıfını genişleten bir sınıf tanımlayın.
- Geçersiz kıl __init__ inşaatçı
- Geçersiz kıl Çalıştırmak() yöntem
Bir iş parçacığı nesnesi oluşturulduktan sonra, Başlat() Bu yöntemin bu etkinliğin yürütülmesine başlamak için kullanılabileceği ve katılmak() yöntemi, mevcut etkinlik bitene kadar diğer tüm kodları engellemek için kullanılabilir.
Şimdi, önceki örneğinizi uygulamak için threading modülünü kullanmayı deneyelim. Tekrar başlatalım: IDLE ve aşağıdakileri yazın:
import time import threading class threadtester (threading.Thread): def __init__(self, id, name, i): threading.Thread.__init__(self) self.id = id self.name = name self.i = i def run(self): thread_test(self.name, self.i, 5) print ("%s has finished execution " %self.name) def thread_test(name, wait, i): while i: time.sleep(wait) print ("Running %s \n" %name) i = i - 1 if __name__=="__main__": thread1 = threadtester(1, "First Thread", 1) thread2 = threadtester(2, "Second Thread", 2) thread3 = threadtester(3, "Third Thread", 3) thread1.start() thread2.start() thread3.start() thread1.join() thread2.join() thread3.join()
Yukarıdaki kodu çalıştırdığınızda çıktı bu olacaktır:
KOD AÇIKLAMA
- Bu kısım önceki örneğimizle aynı. Burada, yürütme ve gecikmeleri yönetmek için kullanılan zaman ve iş parçacığı modüllerini içe aktarıyorsunuz. Python İş Parçacığı.
- Bu bitte, threadtester adında, miras alan veya genişleten bir sınıf yaratıyorsunuz. Konu iş parçacığı modülünün sınıfı. Bu, iş parçacığı oluşturmanın en yaygın yollarından biridir. Python. Ancak, yalnızca yapıcıyı ve Çalıştırmak() uygulamanızda yöntem. Yukarıdaki kod örneğinde görebileceğiniz gibi, __init__ yöntem (yapıcı) geçersiz kılındı. Benzer şekilde, şunu da geçersiz kıldınız: Çalıştırmak() yöntem. Bir iş parçacığının içinde çalıştırmak istediğiniz kodu içerir. Bu örnekte thread_test() fonksiyonunu çağırdınız.
- Bu, thread_test() metodudur ve şu değeri alır: i Bir argüman olarak alınan i değerini her yinelemede 1 azaltır ve i 0 olana kadar kodun geri kalanını döngüye alır. Her yinelemede, şu anda çalışan iş parçacığının adını yazdırır ve bekleme saniyesi kadar (bu da bir argüman olarak alınır) uyur.
- thread1 = threadtester(1, “First Thread”, 1) Burada bir thread oluşturuyoruz ve __init__'de bildirdiğimiz üç parametreyi geçiyoruz. İlk parametre iş parçacığının kimliği, ikinci parametre iş parçacığının adı ve üçüncü parametre ise while döngüsünün kaç kez çalışması gerektiğini belirleyen sayaçtır.
- thread2.start() yöntemi, bir iş parçacığının yürütülmesini başlatmak için kullanılır. Dahili olarak, start() fonksiyonu sınıfınızın run() yöntemini çağırır.
- thread3.join() join() yöntemi diğer kodun yürütülmesini engeller ve çağrıldığı iş parçacığının tamamlanmasını bekler.
Bildiğiniz gibi, aynı işlemde bulunan iş parçacıkları o işlemin belleğine ve verilerine erişebilir. Sonuç olarak, birden fazla iş parçacığı aynı anda verileri değiştirmeye veya erişmeye çalışırsa, hatalar ortaya çıkabilir.
Sonraki bölümde, iş parçacıklarının verilere erişirken ortaya çıkabilecek farklı komplikasyon türlerini ve mevcut erişim işlemlerini kontrol etmeden kritik bölümü göreceksiniz.
Kilitlenmeler ve Yarış koşulları
Kilitlenmeler ve yarış durumları hakkında bilgi edinmeden önce, eşzamanlı programlamayla ilgili birkaç temel tanımı anlamak faydalı olacaktır:
- Kritik Bölüm: Paylaşılan değişkenlere erişen veya bunları değiştiren ve atomik bir işlem olarak gerçekleştirilmesi gereken bir kod parçasıdır.
- Bağlam Değiştirme: Bu, bir işlemcinin bir görevden diğerine geçmeden önce bir iş parçacığının durumunu saklamak için izlediği süreçtir, böylece daha sonra aynı noktadan devam edilebilir.
kilitlenmeler
kilitlenmeler geliştiricilerin eşzamanlı/çok iş parçacıklı uygulamalar yazarken karşılaştıkları en korkulan sorundur. PythonKilitlenmeleri anlamanın en iyi yolu, bilgisayar bilimlerinde klasik bir örnek problem olan kilitlenme problemini kullanmaktır. Gastronomi Philosophers Sorunu.
Yemek felsefecileri için problem ifadesi şu şekildedir:
Şemada gösterildiği gibi, beş filozof yuvarlak bir masada beş tabak spagetti (bir çeşit makarna) ve beş çatalla oturmaktadır.
Gastronomi Philosophers Sorunu
Bir filozofun herhangi bir anda ya yemek yediği ya da düşündüğü anlaşılıyor.
Dahası, bir filozof spagettiyi yiyebilmek için önce yanındaki iki çatalı (yani sol ve sağ çatalı) almak zorundadır. Kilitlenme sorunu, beş filozofun da sağ çatallarını aynı anda almasıyla ortaya çıkar.
Filozofların her birinin bir çatalı olduğundan, hepsi diğerlerinin çatallarını bırakmasını bekleyeceklerdir. Sonuç olarak, hiçbiri spagetti yiyemeyecektir.
Benzer şekilde, eşzamanlı bir sistemde, farklı iş parçacıkları veya süreçler (filozoflar) aynı anda paylaşılan sistem kaynaklarını (çatallar) edinmeye çalıştığında bir çıkmaz meydana gelir. Sonuç olarak, süreçlerin hiçbiri başka bir süreç tarafından tutulan başka bir kaynağı bekledikleri için yürütülme şansı elde edemez.
Yarış koşulları
Yarış durumu, bir sistemin aynı anda iki veya daha fazla işlem gerçekleştirmesi durumunda ortaya çıkan istenmeyen bir program durumudur. Örneğin, şu basit for döngüsünü ele alalım:
i=0; # a global variable for x in range(100): print(i) i+=1;
eğer yaratırsan n Bu kodu aynı anda çalıştıran iş parçacığı sayısı çok fazla olduğundan, programın yürütülmesi bittiğinde (iş parçacıkları tarafından paylaşılan) i değerini belirleyemezsiniz. Bunun nedeni, gerçek bir çoklu iş parçacığı ortamında, iş parçacıklarının üst üste gelebilmesi ve bir iş parçacığı tarafından alınan ve değiştirilen i değerinin, başka bir iş parçacığı ona eriştiğinde arada değişebilmesidir.
Bunlar, çok iş parçacıklı veya dağıtılmış bir sistemde meydana gelebilecek iki ana sorun sınıfıdır. Python Uygulama. Sonraki bölümde, iş parçacıklarını senkronize ederek bu sorunun nasıl üstesinden gelineceğini öğreneceksiniz.
Synckonuları hronize etmek
Yarış koşulları, çıkmazlar ve diğer iş parçacığı tabanlı sorunlarla başa çıkmak için iş parçacığı modülü şunları sağlar: kilitlemek nesne. Fikir, bir iş parçacığının belirli bir kaynağa erişmek istemesi durumunda, o kaynak için bir kilit edinmesidir. Bir iş parçacığı belirli bir kaynağı kilitlediğinde, kilit serbest bırakılana kadar başka hiçbir iş parçacığı ona erişemez. Sonuç olarak, kaynaktaki değişiklikler atomik olacak ve yarış koşulları önlenecektir.
Bir kilit, tarafından uygulanan düşük seviyeli bir senkronizasyon ilkelidir. _iplik Modül. Bir kilit, herhangi bir anda iki durumdan birinde olabilir: kilitli or kilitsiz. İki yöntemi destekler:
- elde etmek(): Kilit durumu kilitli değilse, acquire() metodunu çağırmak durumu kilitli olarak değiştirir ve geri döner. Ancak, durum kilitliyse, acquire() çağrısı, başka bir iş parçacığı tarafından release() metodu çağrılana kadar engellenir.
- serbest bırakmak(): Release() yöntemi, durumu kilitsiz olarak ayarlamak, yani bir kilidi serbest bırakmak için kullanılır. Kilidi alan herhangi bir iş parçacığı tarafından çağrılabilir.
İşte uygulamalarınızda kilit kullanımına dair bir örnek. Uygulamanızı açın. IDLE ve aşağıdakileri yazın:
import threading lock = threading.Lock() def first_function(): for i in range(5): lock.acquire() print ('lock acquired') print ('Executing the first funcion') lock.release() def second_function(): for i in range(5): lock.acquire() print ('lock acquired') print ('Executing the second funcion') lock.release() if __name__=="__main__": thread_one = threading.Thread(target=first_function) thread_two = threading.Thread(target=second_function) thread_one.start() thread_two.start() thread_one.join() thread_two.join()
Şimdi F5'e basın. Bunun gibi bir çıktı görmelisiniz:
KOD AÇIKLAMA
- Burada sadece çağrı yaparak yeni bir kilit oluşturuyorsunuz. iş parçacığı.Lock() fabrika işlevi. Dahili olarak Lock(), platform tarafından sağlanan en etkili somut Lock sınıfının bir örneğini döndürür.
- İlk ifadede, gain() yöntemini çağırarak kilidi elde edersiniz. Kilit verildiğinde yazdırırsınız “kilit alındı” konsola. İş parçacığının çalışmasını istediğiniz tüm kodun yürütmesi bittiğinde, Release() yöntemini çağırarak kilidi serbest bırakırsınız.
Teori güzel, ama kilidin gerçekten işe yaradığını nasıl anlıyorsunuz? Çıktıya bakarsanız, her bir print ifadesinin tam olarak birer satır yazdırdığını göreceksiniz. Daha önceki bir örnekte, birden fazla iş parçacığı aynı anda print() yöntemine eriştiği için print çıktılarının düzensiz olduğunu hatırlayın. Burada, print fonksiyonu yalnızca kilit alındıktan sonra çağrılıyor. Bu nedenle, çıktılar tek tek ve satır satır görüntüleniyor.
Kilitlerin yanı sıra Python Aşağıda listelenen diğer bazı mekanizmaları da destekleyerek iş parçacığı senkronizasyonunu yönetir:
- RKilitler
- Semaphores
- Koşullar
- Etkinlikler ve
- Bariyerler
Küresel Tercüman Kilidi (ve bununla nasıl başa çıkılacağı)
Ayrıntılara girmeden önce PythonGIL'e geçmeden önce, önümüzdeki bölümü anlamada faydalı olacak birkaç terimi tanımlayalım:
- İşlemciye bağlı kod: Bu, doğrudan işlemci tarafından yürütülecek herhangi bir kod parçasını ifade eder.
- G/Ç odaklı kod: Bu, işletim sistemi aracılığıyla dosya sistemine erişen herhangi bir kod olabilir.
- CPython: bu referanstır uygulama of Python ve C ile yazılmış yorumlayıcı olarak tanımlanabilir ve Python (programlama dili).
GIL nedir? Python?
Küresel Tercüman Kilidi (GIL) in Python GIL, süreçlerle ilgilenirken kullanılan bir süreç kilidi veya mutex'tir. Bir seferde yalnızca bir iş parçacığının belirli bir kaynağa erişebilmesini sağlar ve nesnelerin ve bayt kodlarının aynı anda kullanılmasını engeller. Bu, tek iş parçacıklı programlara performans artışı sağlar. GIL'in işlevi budur. Python uygulaması oldukça basit ve kolaydır.
Belirli bir zamanda yalnızca bir iş parçacığının belirli bir kaynağa erişebildiğinden emin olmak için kilit kullanılabilir.
özelliklerinden biri Python her tercüman sürecinde global bir kilit kullanmasıdır; bu da her sürecin, Python tercümanın kendisi bir kaynaktır.
Örneğin, bir yazı yazdığınızı varsayalım. Python Hem CPU hem de G/Ç işlemlerini gerçekleştirmek için iki iş parçacığı kullanan bir program. Bu programı çalıştırdığınızda şunlar olur:
- MKS Python Yorumlayıcı yeni bir işlem oluşturur ve iş parçacıklarını başlatır.
- İş parçacığı-1 çalışmaya başladığında ilk önce GIL'yi alacak ve kilitleyecektir.
- Eğer iş parçacığı-2 şimdi yürütülmek istiyorsa, başka bir işlemci boş olsa bile GIL'in serbest bırakılmasını beklemek zorunda kalacak.
- Şimdi iş parçacığı-1'in bir G/Ç işlemini beklediğini varsayalım. Şu anda GIL'i serbest bırakacak ve iş parçacığı-2 onu alacak.
- G/Ç işlemlerini tamamladıktan sonra, eğer iş parçacığı-1 şimdi yürütmek istiyorsa, yine GIL'in iş parçacığı-2 tarafından serbest bırakılmasını beklemek zorunda kalacak.
Bu nedenle, herhangi bir zamanda yorumlayıcıya yalnızca bir iş parçacığı erişebilir, bu da yalnızca bir iş parçacığının yürütüleceği anlamına gelir. Python Belirli bir anda kodun yazılması.
Tek çekirdekli işlemcilerde bu sorun teşkil etmez çünkü işlemci, iş parçacıklarını yönetmek için zaman dilimleme (bu eğitimin ilk bölümüne bakın) yöntemini kullanır. Ancak, çok çekirdekli işlemcilerde, birden fazla iş parçacığında çalışan işlemciye bağımlı bir fonksiyon, programın verimliliğini önemli ölçüde etkileyecektir çünkü aynı anda mevcut tüm çekirdekleri kullanmayacaktır.
GIL'e neden ihtiyaç duyuldu?
CPython Çöp toplayıcı, referans sayımı olarak bilinen verimli bir bellek yönetimi tekniği kullanır. İşte çalışma prensibi: Her nesne Python Bir nesnenin referans sayısı vardır ve bu sayı, yeni bir değişken adına atandığında veya bir kapsayıcıya (örneğin demetler, listeler vb.) eklendiğinde artar. Benzer şekilde, referans kapsam dışına çıktığında veya `del` ifadesi çağrıldığında referans sayısı azalır. Bir nesnenin referans sayısı 0'a ulaştığında, çöp toplama işlemi yapılır ve ayrılan bellek serbest bırakılır.
Ancak sorun, referans sayısı değişkeninin diğer küresel değişkenler gibi yarış koşullarına yatkın olmasıdır. Bu sorunu çözmek için geliştiriciler Python Küresel yorumlayıcı kilidini kullanmaya karar verdik. Diğer seçenek ise her nesneye bir kilit eklemekti; bu da kilitlenmelere ve acquire() ve release() çağrılarından kaynaklanan ek yükün artmasına neden olacaktı.
Bu nedenle GIL, çok iş parçacıklı uygulamalar için önemli bir kısıtlamadır. Python Yoğun işlemci kullanımı gerektiren işlemleri çalıştıran programlar (bu da onları tek iş parçacıklı hale getirir). Uygulamanızda birden fazla işlemci çekirdeği kullanmak istiyorsanız, şunu kullanın: çoklu işlem modül yerine.








