Python İş Parçacığı: Bir Giriş

Yayınlanan: 2022-10-25

Bu öğreticide, Python'daki çoklu iş parçacığı özelliklerini keşfetmek için Python'un yerleşik iş parçacığı modülünü nasıl kullanacağınızı öğreneceksiniz.

İşlemlerin ve iş parçacıklarının temellerinden başlayarak, eşzamanlılık ve paralellik kavramlarını anlarken Python'da çoklu iş parçacığının nasıl çalıştığını öğreneceksiniz. Daha sonra yerleşik threading modülünü kullanarak Python'da bir veya daha fazla iş parçacığını nasıl başlatacağınızı ve çalıştıracağınızı öğreneceksiniz.

Başlayalım.

Süreçler ve Konular: Farklar Nelerdir?

Süreç Nedir?

İşlem , çalışması gereken bir programın herhangi bir örneğidir.

Herhangi bir şey olabilir – bir Python betiği veya Chrome gibi bir web tarayıcısından bir video konferans uygulamasına. Makinenizde Görev Yöneticisini başlatır ve Performans –> CPU'ya giderseniz, şu anda CPU çekirdeklerinizde çalışmakta olan süreçleri ve iş parçacıklarını görebileceksiniz.

cpu-proc-threads

Süreçleri ve Konuları Anlama

Dahili olarak, bir işlem, işleme karşılık gelen kodu ve verileri depolayan özel bir belleğe sahiptir.

Bir işlem bir veya daha fazla iş parçacığından oluşur. Bir iş parçacığı, işletim sisteminin yürütebileceği en küçük talimat dizisidir ve yürütme akışını temsil eder.

Her iş parçacığının kendi yığını ve kayıtları vardır, ancak ayrılmış bir belleği yoktur. Bir işlemle ilişkili tüm iş parçacıkları verilere erişebilir. Bu nedenle, veri ve bellek, bir işlemin tüm iş parçacıkları tarafından paylaşılır.

süreç ve iş parçacıkları

N çekirdekli bir CPU'da, N süreç aynı anda paralel olarak yürütülebilir. Ancak, aynı işlemin iki iş parçacığı asla paralel olarak yürütülemez, ancak eşzamanlı olarak yürütülebilir. Bir sonraki bölümde eşzamanlılık ve paralellik kavramını ele alacağız.

Şimdiye kadar öğrendiklerimize dayanarak, bir süreç ile bir iş parçacığı arasındaki farkları özetleyelim.

Özellik İşlem İplik
Hafıza Adanmış hafıza Paylaşılan hafıza
Yürütme modu paralel, eşzamanlı Eşzamanlı; ama paralel değil
Yürütme tarafından işlenir İşletim sistemi CPython Tercümanı

Python'da çoklu kullanım

Python'da Global Yorumlayıcı Kilidi (GIL), herhangi bir zamanda yalnızca bir iş parçacığının kilidi almasını ve çalışmasını sağlar. Tüm iş parçacıklarının çalışması için bu kilidi edinmesi gerekir. Bu, herhangi bir zamanda yalnızca tek bir iş parçacığının yürütülebilmesini sağlar ve eşzamanlı çoklu iş parçacığını önler.

Örneğin, aynı işlemin iki iş parçacığını, t1 ve t2 düşünün. t1 belirli bir k değerini okurken iş parçacıkları aynı verileri paylaştığından, t2 aynı k değerini değiştirebilir. Bu, kilitlenmelere ve istenmeyen sonuçlara yol açabilir. Ancak iş parçacıklarından yalnızca biri herhangi bir durumda kilidi alabilir ve çalıştırabilir. Bu nedenle GIL ayrıca iplik güvenliğini de sağlar.

Peki Python'da çoklu kullanım yeteneklerini nasıl elde ederiz? Bunu anlamak için eşzamanlılık ve paralellik kavramlarını tartışalım.

Eşzamanlılık ve Paralellik: Genel Bir Bakış

Birden fazla çekirdeğe sahip bir CPU düşünün. Aşağıdaki çizimde CPU'nun dört çekirdeği vardır. Bu, herhangi bir anda paralel olarak çalışan dört farklı işlemimiz olabileceği anlamına gelir.

Dört işlem varsa, işlemlerin her biri dört çekirdeğin her birinde bağımsız ve aynı anda çalışabilir. Her işlemin iki iş parçacığı olduğunu varsayalım.

çok çekirdekli paralellik

İş parçacığı oluşturmanın nasıl çalıştığını anlamak için çok çekirdekli işlemci mimarisinden tek çekirdekli işlemci mimarisine geçelim. Belirtildiği gibi, belirli bir yürütme örneğinde yalnızca tek bir iş parçacığı etkin olabilir; ancak işlemci çekirdeği iş parçacıkları arasında geçiş yapabilir.

kod

Örneğin, G/Ç'ye bağlı iş parçacıkları genellikle G/Ç işlemlerini bekler: kullanıcı girdisinde okuma, veritabanı okumaları ve dosya işlemleri. Bu bekleme süresi boyunca, diğer iş parçacığının çalışabilmesi için kilidi serbest bırakabilir . Bekleme süresi, n saniye uyumak gibi basit bir işlem de olabilir.

Özetle: Bekleme işlemleri sırasında, iş parçacığı kilidi serbest bırakarak işlemci çekirdeğinin başka bir iş parçacığına geçmesini sağlar. Bekleme süresi tamamlandıktan sonra önceki iş parçacığı yürütmeye devam eder. İşlemci çekirdeğinin aynı anda iş parçacıkları arasında geçiş yaptığı bu işlem, çoklu iş parçacığını kolaylaştırır.

Uygulamanızda süreç düzeyinde paralellik uygulamak istiyorsanız, bunun yerine çoklu işlemeyi kullanmayı düşünün.

Python Threading Modülü: İlk Adımlar

Python, Python betiğine aktarabileceğiniz bir threading modülüyle birlikte gelir.

 import threading

Python'da bir thread nesnesi oluşturmak için Thread yapıcısını kullanabilirsiniz: threading.Thread(...) . Bu, çoğu iş parçacığı uygulaması için yeterli olan genel sözdizimidir:

 threading.Thread(target=...,args=...)

Burada,

  • target , çağrılabilir bir Python'u gösteren anahtar kelime argümanıdır.
  • args , hedefin aldığı argümanlar dizisidir.

Bu öğreticideki kod örneklerini çalıştırmak için Python 3.x'e ihtiyacınız olacak. Kodu indirin ve devam edin.

Python'da Konular Nasıl Tanımlanır ve Çalıştırılır

Hedef işlevi çalıştıran bir iş parçacığı tanımlayalım.

Hedef işlev some_func .

 import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() print(threading.active_count())

Yukarıdaki kod parçacığının ne yaptığını ayrıştıralım:

  • İş threading ve time modüllerini içe aktarır.
  • some_func işlevi, açıklayıcı print() ifadelerine sahiptir ve iki saniyelik bir uyku işlemi içerir: time.sleep(n) , işlevin n saniye boyunca uyumasına neden olur.
  • Ardından, hedefi thread_1 olan bir some_func iş parçacığı tanımlarız. threading.Thread(target=...) bir thread nesnesi yaratır.
  • Not : Bir işlev çağrısı değil, işlevin adını belirtin; some_func kullanın ve some_func() değil .
  • Bir iş parçacığı nesnesi oluşturmak, bir parçacığı başlatmaz; thread nesnesinde start() yöntemini çağırmak işe yarar.
  • Aktif iş parçacığı sayısını almak için active_count() işlevini kullanırız.

Python betiği ana iş parçacığı üzerinde çalışıyor ve aktif iş parçacığı sayısı çıktıda görüldüğü gibi iki olacak şekilde some_func işlevini çalıştırmak için başka bir iş parçacığı ( thread1 ) oluşturuyoruz:

 # Output Running some_func... 2 Finished running some_func.

Çıktıya daha yakından bakarsak, thread1 başlatıldığında ilk print ifadesinin çalıştığını görürüz. Ancak uyku işlemi sırasında, işlemci ana iş parçacığına geçer ve iş parçacığı1'in yürütmeyi thread1 beklemeden etkin iş parçacıklarının sayısını yazdırır.

thread1-ex

İş Parçacıklarının Yürütülmesini Bitirmesini Bekliyor

thread1 yürütmeyi bitirmesini istiyorsanız, thread'i başlattıktan sonra bunun üzerinde join() yöntemini çağırabilirsiniz. Bunu yapmak, ana iş parçacığına geçmeden iş parçacığı1'in yürütmeyi thread1 bekleyecektir.

 import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() thread1.join() print(threading.active_count())

Şimdi, aktif iş parçacığı sayısını yazdırmadan önce iş thread1 yürütmeyi bitirdi. Yani sadece ana iş parçacığı çalışıyor, bu da aktif iş parçacığı sayısının bir olduğu anlamına geliyor.

 # Output Running some_func... Finished running some_func. 1

Python'da Birden Çok Konu Nasıl Çalıştırılır

Ardından, iki farklı işlevi çalıştırmak için iki iş parçacığı oluşturalım.

Burada geri sayım, argüman olarak bir sayıyı alan ve o sayıdan sıfıra doğru geri count_down bir fonksiyondur.

 def count_down(n): for i in range(n,-1,-1): print(i)

Sıfırdan belirli bir sayıya kadar sayan başka bir Python işlevi olan count_up tanımlarız.

 def count_up(n): for i in range(n+1): print(i)

range() işlevi range(start, stop, step) sözdizimi ile kullanıldığında, bitiş noktası stop varsayılan olarak hariç tutulur.

– Belirli bir sayıdan sıfıra geri saymak için, -1'lik bir negatif step değeri kullanabilir ve sıfırın dahil edilmesi için stop değerini -1'e ayarlayabilirsiniz.

– Benzer şekilde, n kadar saymak için stop değerini n + 1 olarak ayarlamanız gerekir. start ​​ve step varsayılan değerleri sırasıyla 0 ve 1 olduğundan, 0'dan n'ye kadar olan diziyi elde etmek için range(n + 1) kullanabilirsiniz.

Ardından, sırasıyla count_down ve count_up işlevlerini çalıştırmak için thread1 ve thread2 olmak üzere iki thread tanımlarız. Her iki fonksiyon için de print deyimleri ve sleep işlemleri ekliyoruz.

İş parçacığı nesnelerini oluştururken, hedef işlevin argümanlarının bir tanımlama grubu olarak ( args parametresine) belirtilmesi gerektiğine dikkat edin. Her iki işlev de ( count_down ve count_up ) tek bir argüman alır. Değerden sonra açıkça bir virgül eklemeniz gerekir. Bu, sonraki öğeler None olarak çıkarıldığından, argümanın hala bir demet olarak iletilmesini sağlar.

 import threading import time def count_down(n): for i in range(n,-1,-1): print("Running thread1....") print(i) time.sleep(1) def count_up(n): for i in range(n+1): print("Running thread2...") print(i) time.sleep(1) thread1 = threading.Thread(target=count_down,args=(10,)) thread2 = threading.Thread(target=count_up,args=(5,)) thread1.start() thread2.start()

Çıktıda:

  • count_up işlevi count_up üzerinde çalışır ve thread2 başlayarak 5'e kadar sayar.
  • count_down işlevi, thread1 10'dan 0'a geri sayım üzerinde çalışır.
 # Output Running thread1.... 10 Running thread2... 0 Running thread1.... 9 Running thread2... 1 Running thread1.... 8 Running thread2... 2 Running thread1.... 7 Running thread2... 3 Running thread1.... 6 Running thread2... 4 Running thread1.... 5 Running thread2... 5 Running thread1.... 4 Running thread1.... 3 Running thread1.... 2 Running thread1.... 1 Running thread1.... 0

Her ikisi de bir bekleme işlemi (uyku) içerdiğinden, thread1 ve thread2 alternatif olarak yürütüldüğünü görebilirsiniz. count_up işlevi 5'e kadar saymayı bitirdiğinde, thread2 artık aktif değildir. Böylece sadece thread1 karşılık gelen çıktıyı elde ederiz.

Özetliyor

Bu öğreticide, çoklu iş parçacığını uygulamak için Python'un yerleşik iş parçacığı modülünü nasıl kullanacağınızı öğrendiniz. İşte önemli çıkarımların bir özeti:

  • Thread yapıcısı, bir thread nesnesi oluşturmak için kullanılabilir. threading.Thread(target=<callable>,args=(<tuple of args>)) kullanımı , args içinde belirtilen argümanlarla çağrılabilir hedefi çalıştıran bir iş parçacığı oluşturur.
  • Python programı bir ana iş parçacığı üzerinde çalışır, bu nedenle oluşturduğunuz iş parçacığı nesneleri ek iş parçacıklarıdır. Active_count() işlevini çağırabilirsiniz, herhangi bir örnekte aktif iş parçacıklarının sayısını döndürür.
  • İş parçacığı nesnesinde start() yöntemini kullanarak bir iş parçacığı başlatabilir ve join() yöntemini kullanarak yürütmenin bitmesini bekleyebilirsiniz.

Bekleme sürelerini değiştirerek, farklı bir G/Ç işlemi deneyerek ve daha fazlasını yaparak ek örnekler kodlayabilirsiniz. Yaklaşan Python projelerinizde çoklu iş parçacığı uyguladığınızdan emin olun. Mutlu kodlama!