Python'da Eş Zamanlılık - Çoklu İşlem
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 çok 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 paralel olarak Python kodunu çalıştırmasını engellediğini söyleyebiliriz. Kilit, bir seferde yalnızca bir iş parçacığı tarafından tutulabilir ve eğer bir iş parçacığı yürütmek istiyorsak, önce kilidi almalıdır.
Çoklu işlemin kullanımı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ç, eşzamansız olarak yürütülmesine 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 betiği ö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 süreci 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 oluşturulanla 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ı işlemi, ç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. Ayrıca, 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 engeller. 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ıldı, çünkü girdi, 0'dan 4'e kadar olan tamsayıların bir listesidir. Sonuç, p_outputs ve basılmıştı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]