Ana içeriğe geç

Python Çoklu İşlem: İş Parçacıkları ve Süreçlere Kılavuz

Python’ın multiprocessing modülü ile iş parçacıklarını ve süreçleri nasıl yöneteceğinizi öğrenin. Paralel programlama için temel teknikleri keşfedin. Örneklerle kodunuzun verimliliğini artırın.
· 7 dk. oku

Yapay Zeka ile Keşfedin

Python’ın standart kitaplığı, geliştiricilerin dilin avantajlarından anında yararlanmaya başlaması için birkaç yerleşik paketle birlikte gelir.

Bu paketlerden biri, sistemlerin birden fazla süreci eşzamanlı olarak çalıştırmasını sağlayan multiprocessing modülüdür. Başka bir deyişle, geliştiriciler uygulamaları Python kodlarından bağımsız olarak çalışabilen daha küçük iş parçacıklarına bölebilir. Bu iş parçacıkları veya süreçler daha sonra işletim sistemi tarafından işlemciye tahsis edilir ve paralel olarak çalıştırılarak Python programlarınızın performansını ve verimliliğini artırır. 

İş parçacıkları, süreçler, işlemciler gibi kelimeler size yabancıysa endişelenmeyin. Bu yazıda bir sürecin tanımını, iş parçacıklarından nasıl farklılaştığını ve multiprocessing modülünün nasıl kullanılacağını ele alacağız. 

Python’da Süreçler Nedir?

Süreçler ve iş parçacıkları kavramını anlamak, bir işletim sisteminin programları çeşitli yürütme aşamalarında nasıl yönettiğini daha iyi kavramak için son derece faydalıdır. Bir süreç, yalnızca bir bilgisayar programına yapılan bir göndermedir. Her programın onunla ilişkili bir süreci vardır. 

Bu makaleyi okurken, bilgisayarınızın aynı anda birçok süreci çalıştırıyor olması kuvvetle muhtemeldir; hatta yalnızca birkaç program açık olsa bile – bunun nedeni çoğu işletim sisteminin arka planda birkaç görevi çalıştırmasıdır. Örneğin, şu anda yalnızca üç program çalıştırıyor olabilirsiniz ancak bilgisayarınız aynı anda 30’dan fazla etkin süreç çalıştırıyor olabilir. 

Bilgisayarınızda şu anda çalışan etkin süreçleri nasıl kontrol edeceğiniz, işletim sisteminize bağlıdır: 

  • Windows’ta: Görev Yöneticisi’ni başlatmak için Ctrl+Shift+Esc 
  • Mac’te: Spotlight Araması’nı açın ve "Activity Monitor" yazıp Return tuşuna basın  
  • Linux’ta: Uygulama Menüsü’ne tıklayın ve System Monitor’ü arayın. 

Python, gerçek sistem düzeyindeki süreçlere erişim sağlar. Multiprocessing modülündeki Process sınıfından bir örnek oluşturmak, geliştiricilerin temel yerel süreci Python kullanarak referans almasına imkân tanır. Bir süreç başlatıldığında, perde arkasında yeni bir yerel süreç oluşturulur. Bir Python sürecinin yaşam döngüsü üç aşamadan oluşur: Yeni bir sürecin başlatılması, sürecin çalışması ve sürecin sonlandırılması – her aşamayı ele alacağız.

Tüm süreçler bir veya daha fazla iş parçacığından oluşur. “Bir sürecin yalnızca bir bilgisayar programına yapılan bir gönderme olduğunu” söylemiştik, hatırlıyor musunuz? Aslında her Python programı, ana iş parçacığı adı verilen varsayılan bir iş parçacığından oluşan bir süreçtir. Ana iş parçacığı, Python programlarınızdaki talimatları yürütmekten sorumludur. Ancak süreçler ve iş parçacıklarının farklı olduğunu unutmamak gerekir. 

Multiprocessing vs. Threading

Daha net olmak gerekirse, Python yorumlayıcısının bir örneği – Python ile yazılan kodu bir bilgisayarın anlayabileceği dile çeviren araç – bir sürece eşittir. Bir süreç, Python’da “ana iş parçacığı” olarak adlandırılan en az bir iş parçacığından oluşur; aynı süreç içinde başka iş parçacıkları da oluşturulabilir – bir süreç içinde oluşturulan diğer tüm iş parçacıkları o sürece ait olacaktır. 

İş parçacığı, Python programınızın nasıl yürütüleceğinin bir temsili olarak görev yapar ve arka plan dışındaki tüm iş parçacıkları sonlandırıldığında Python süreci de sonlanır. 

  • Süreç: Bir süreç, en az ana iş parçacığı adı verilen bir iş parçacığından oluşan Python yorumlayıcısının bir örneğidir. 
  • İş parçacığı: Bir Python programının bir Python süreci içinde nasıl yürütüldüğünün temsili.

Python, süreçler ve iş parçacıkları üzerinde daha fazla kontrol sağlayan iki birbirine çok benzer sınıfa sahiptir: multiprocessing.Process ve threading.Thread.

Benzerliklerini ve farklılıklarını gözden geçirelim. 

Benzerlikler

Eşzamanlılık

Eşzamanlılık, bir programın farklı parçalarının nihai sonucu etkilemeden sırayla veya kısmi sırayla yürütülebilmesi kavramıdır. Her iki sınıf da başlangıçta eşzamanlılık için tasarlanmıştır. 

Eşzamanlılık ilkel desteği

multiprocessing.Process ve threading.Thread sınıfları aynı eşzamanlılık ilkel yapılarını destekler – iş parçacıklarının ve süreçlerin senkronizasyonunu ve koordinasyonunu sağlayan araçlar. 

Birbirine uyumlu API

multiprocessing.Process API’sini kavradıktan sonra bu bilgiyi threading.Thread API’sine de aktarabilirsiniz ve tersi de geçerlidir. Bilerek bu şekilde tasarlanmışlardır. 

Farklılıklar

İşlevsellik

API’leri aynı olsa da süreçler ve iş parçacıkları farklıdır. Bir süreç, bir iş parçacığından daha yüksek bir soyutlama düzeyidir: süreç bir bilgisayar programına göndermedir ve iş parçacığı bir sürece aittir. Bu fark sınıfların doğasında vardır. Dolayısıyla bu sınıflar, alttaki işletim sistemi tarafından yönetilen iki farklı yerel işlevi temsil eder. 

Paylaşılan duruma erişim

İki sınıf paylaşılan duruma farklı şekillerde erişir. İş parçacıkları bir sürece ait olduğundan, bir süreç içinde belleği paylaşabilirler. Dolayısıyla yeni bir iş parçacığında yürütülen bir işlev, süreç içindeki aynı veri ve duruma hâlâ erişebilir. İş parçacıklarının durumları birbirleriyle paylaşma yöntemi “paylaşımlı bellek” olarak bilinir ve oldukça düzgündür. Buna karşılık, süreçler arasında durum paylaşımı çok daha meşakkatlidir: durumun serileştirilip süreçler arasında iletilmesi gerekir. Başka bir deyişle, süreçler ayrı belleklere sahip olduklarından durumu paylaşmak için paylaşımlı bellek kullanmaz. Bunun yerine süreçler, “süreçler arası iletişim” adı verilen bir teknikle paylaşılır ve bunu Python’da gerçekleştirmek için multiprocessing.Pipe veya multiprocessing.Queue gibi başka açık araçlar gerekir. 

GIL

Python Global Interpreter Lock (GIL), Python yorumlayıcısı üzerinde aynı anda yalnızca bir iş parçacığının denetim sahibi olmasına izin veren bir kilittir. Birden çok iş parçacığı GIL’e tabidir; bu da Python’ı çoklu iş parçacığı için kullanmayı çoğu zaman kötü bir fikir hâline getirir: CPython yorumlayıcısında çoklu iş parçacığıyla gerçek çok çekirdekli yürütme desteklenmez. Ancak süreçler GIL’in etkisine tabi değildir çünkü GIL her Python sürecinin içinde kullanılır, süreçler arasında değil. 

Örnek kullanım açısından, programın CPU yoğun olduğu ve herhangi bir IO veya kullanıcı etkileşimi gerçekleştirmesinin gerekmediği senaryolarda multiprocessing çoğunlukla threading’den daha iyi performans gösterir. IO veya ağ sınırlı programlar ve amacı uygulamayı daha duyarlı hâle getirmek olan senaryolar için ise threading daha iyi bir çözümdür. 

Python Multiprocessing’in Faydaları

Bir işlemciyi bir girişimci olarak düşünün. Girişimcinin işi büyüdükçe, işin büyümesine ayak uydurmak için yönetilmesi gereken daha fazla görev ortaya çıkar. Girişimci bu görevlerin hepsini tek başına üstlenmeye karar verirse (örn. muhasebe, satış, pazarlama, inovasyon vb.), tek bir kişinin aynı anda yapabileceklerinin bir sınırı olduğundan işletmenin genel verimliliğini ve performansını baltalama riski taşır. Örneğin, inovasyon görevlerine geçmeden önce satış görevlerini durdurmak zorundadır – buna görevleri “ardışık” olarak çalıştırmak denir. 

Çoğu girişimci her şeyi tek başına yapmaya çalışmanın kötü bir fikir olduğunu bilir. Sonuç olarak genellikle artan görev sayısını, çeşitli departmanları yönetmek üzere çalışanlar işe alarak dengelerler. Bu sayede görevler paralel olarak yapılabilir – yani bir görevin çalışması için diğerinin durmasına gerek kalmaz. Belirli görevleri yerine getirmek üzere daha fazla çalışan işe almak, işlemleri yürütmek için birden fazla işlemci kullanmaya benzer. Örneğin, bilgisayarla görme projeleri oldukça talepkârdır çünkü genellikle çok sayıda görüntü verisini işlemeniz gerekir ve bu zaman alır: Bu süreci hızlandırmak için birden fazla görüntüyü paralel olarak işleyebilirsiniz. 

Dolayısıyla, multiprocessing’in görevleri bölüp farklı işlemcilere atayarak programları daha verimli hâle getirmek için faydalı olduğunu söyleyebiliriz. Python’ın multiprocessing modülü, görevleri farklı süreçlere atayarak programlarınızın verimliliğini artırmak için üst düzey bir araç görevi görerek bunu daha da basitleştirir. 

Python’ın Multiprocessing Modülünün Temelleri 

Bir Python sürecinin yaşam döngüsünün üç aşamadan oluştuğunu söylemiştik: yeni süreç, çalışan süreç ve sonlandırılmış süreç. Bu bölüm, yaşam döngüsünün her aşamasına daha derinlemesine girecek ve kod örnekleri sunacaktır. Adım adım takip etmek için kodlara DataLab çalışma defterimizden erişebilirsiniz. 

Yeni süreç 

Yeni bir süreç, Process sınıfının bir örneğinin başlatılmasıyla oluşturulmuş süreç olarak tanımlanabilir. Process nesnesini bir değişkene atadığımızda bir alt süreç oluşturulur. 

from multiprocessing import Process

# Create a new process
process = Process()

Şu anda süreç örneğimiz hiçbir şey yapmıyor çünkü boş bir Process nesnesi başlattık. Sınıfın target parametresine farklı bir süreçte çalıştırmak istediğimiz bir işlevi geçirerek Process nesnemizin yapılandırmalarını belirli bir işlevi yürütecek şekilde değiştirebiliriz. 

# Create a new process with a specified function to execute.
def example_function():
    pass

new_process = Process(target=example_function)

Hedef işlevimizin parametreleri de olsaydı, bunları Process nesnesinin args parametresine bir demet (tuple) olarak geçirirdik.

İpucu: Writing Functions in Python interaktif kursuyla Python’da nasıl işlev yazılacağını öğrenin. 

# Create a new process with specific function to execute with args.
def example(args):
    pass

process = Process(target=example, args=("Hi",))

Yalnızca yeni bir süreç oluşturduğumuza dikkat edin; henüz çalışmıyor. 

Yeni bir süreci nasıl çalıştırabileceğimize bakalım.  

Çalışan süreç 

Yeni bir süreci çalıştırmak oldukça basittir: süreç örneğinin start() yöntemini çağırmanız yeterlidir. 

# Run the new process
process.start()

 Bu işlem, perde arkasında Process örneğinin run() yöntemini çağırarak sürecin etkinliğini başlatır. run() yöntemi, Process örneğinin target parametresinde belirtilen özel işlevi çağırmaktan da sorumludur (belirtildiyse).

Eğitimin başlarında her sürecin varsayılan olarak ana iş parçacığı adı verilen en az bir iş parçacığına sahip olduğunu söylemiştik. Dolayısıyla bir alt süreç başlatıldığında, o alt süreç için ana iş parçacığı oluşturulur ve başlatılır. Ana iş parçacığı, alt süreçteki tüm kodlarımızı yürütmekten sorumludur. 

start() yöntemi döndükten, alt süreç sonlanana kadar süreç örneğimizin canlı olup olmadığını Process örneğimiz üzerinde is_alive() yöntemini kullanarak kontrol edebiliriz. 

Not: Bunu bir not defterinde (notebook) test ediyorsanız, çalışan süreci yakalayabilmesi için is_alive() çağrısının start() yöntemine yapılan çağrıyla aynı hücrede olması gerekir. 

# Run the new process
process.start()

# Check process is running
process.is_alive()

"""
True
"""

Süreç çalışmıyorsa, is_alive() yöntemine yapılan çağrı False döndürür. 

Sonlandırılmış süreç

run() işlevi döndüğünde veya çıktığında süreç sonlandırılır: mutlaka açıkça bir şey yapmamız gerekmez. Başka bir deyişle, hedef işlev olarak belirlediğiniz talimatlar tamamlandıktan sonra sürecin sonlandırılmasını beklersiniz. Dolayısıyla is_alive() yöntemi false döndürür. 

# Check process is terminated - should return false.
process.is_alive()

"""
False
"""

Ancak, işlenmeyen bir istisna ile karşılaşırsa veya bir hata oluşursa süreç sonlandırılabilir. Örneğin, hedef olarak belirlediğiniz işlevde bir hata varsa süreç sonlanacaktır. 

# Create a new process with a specific function that has an error
# to execute with args.
def example(args):
    split_args = list(args.split())
    # "name" variable is not in the function namespace - should raise error
    return name

# New process
process = Process(target=example, args=("Hi",))

# Running the new process
process.start()

# Check process is running
process.is_alive()

"""
True

Process Process-15:
Traceback (most recent call last):
  File "/usr/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
    self.run()
"""

Process nesnesi ayrıca kullanıcıların bir süreci zorla sonlandırmasını sağlayan terminate() ve kill() yöntemlerine de sahiptir. 

# Create a new process with a specific function to execute with args.
def example(args):
    split_args = list(args.split())
    # "name" variable is not in the function namespace - should raise error
    return split_args

# New process
process = Process(target=example, args=("Hi",))

# Running the new process
process.start()

if process.is_alive():
    process.terminate() # You can also use process.kill()
    print("Process terminated forcefully")

"""
Process terminated forcefully
"""

Zorla sonlandırmaların bir süreci sonlandırmanın önerilen yolu olmadığını unutmamak önemlidir: tüm açık kaynakları güvenli bir şekilde kapatmayabilir veya gerekli program durumunu saklamayabilirler. Daha iyi bir çözüm, süreç açısından güvenli bir boole bayrağı veya benzeri bir araçla kontrollü bir kapatma kullanmaktır. 

Python Multiprocessing Eğitimi

Artık multiprocessing’in temellerini anladığınıza göre, Python’da eşzamanlı programlamanın nasıl yapılacağını göstermek için bir örnek üzerinde çalışalım. 

Oluşturacağımız işlev yalnızca bir ifade yazdıracak, 1 saniye uyuyacak, ardından başka bir ifade yazdıracak – işlevler hakkında daha fazla bilgi için bu Python işlevleri eğitimine göz atın. 

import time

def do_something():
    print("I'm going to sleep")
    time.sleep(1)
    print("I'm awake") 

İlk adım yeni bir süreç oluşturmaktır: ikisini oluşturacağız. 

# Create new child process

process_1 = Process(target=do_something)
process_2 = Process(target=do_something)

Bir not defteri ortamında eşzamanlı bir program şöyle görünecektir: 

%%time

# Starts both processes
process_1.start()
process_2.start()

"""
I'm going to sleep
CPU times: user 810 µs, sys: 7.34 ms, total: 8.15 ms
Wall time: 6.04 ms
I'm going to sleep
"""

Programın çıktısına bakıldığında, kodumuzda bir yerde sorun olduğu oldukça açıktır. Zamanlayıcı ilk sürecimizin ortasında yazdırılıyor ve ikinci yazdırma ifadesi yazdırılmıyor. 

Bu durum, üç sürecin çalışıyor olmasından kaynaklanır: ana süreç ile process_1 ve process_2. Zamanı izleyen ve yazdıran süreç ana süreçtir. Ana sürecimizin zamanı yazdırmadan önce beklemesi için, iki sürecimizi çalıştırdıktan sonra join() yöntemini çağırmalıyız.

Not: Daha fazlasını öğrenmek istiyorsanız bu Stackoverflow tartışmasına göz atın. 

Yeni kod parçacığımıza bakalım: 

%%time
​
# Create new child process (Cannot run a process more than once)
new_process_1 = Process(target=do_something)
new_process_2 = Process(target=do_something)
​
# Starts both processes
new_process_1.start()
new_process_2.start()
​
new_process_1.join()
new_process_2.join()

"""
I'm going to sleep
I'm going to sleep
I'm awake
I'm awake
CPU times: user 0 ns, sys: 14 ms, total: 14 ms
Wall time: 1.01 s
"""

Sorun çözüldü. 

Bu çalıştırmanın duvar saati süresi ilk çalıştırmadan biraz daha uzundu. Ancak bu çalıştırma, zaman bilgisini döndürmeden önce her iki hedef işlevimize yapılan çağrıları da tamamladı. Aynı mantığı daha fazla sürecin eşzamanlı çalışmasını sağlamak için de uygulayabiliriz. 

Python Multiprocessing Hakkında Son Düşünceler

Bu eğitimde Python programlarını eşzamanlı çalıştırarak nasıl daha verimli hâle getirebileceğinizi öğrendiniz. Özellikle şunları öğrendiniz: 

  • Süreçlerin ne olduğu ve bunları bilgisayarınızda nasıl görüntüleyebileceğiniz.
  • Python’ın multiprocessing ve threading modülleri arasındaki benzerlikler ve farklılıklar.
  • multiprocessing modülünün temelleri ve Python programını multiprocessing kullanarak nasıl eşzamanlı çalıştıracağınız. 

Python ile Programlama hakkında daha fazla bilgi edinmek ister misiniz? Python Programmer kariyer yolumuzu inceleyin. Önceden programlama deneyimi gerekmez ve yolun sonunda Python ile yazılım geliştirmek, veri hazırlamak ve ileri düzey analizler yapmak için gerekli becerileri kazanmış olacaksınız. 

Kurtis Pykes

Yazar

Kurtis Pykes

Konular

İlgili