Python ile Aynı Anda Birden Çok İşlem (Multithreading)
- July 7, 2012
Bazı durumlarda ana programın çalışmasını etkilemeden, arkaplanda başka işlemlerin yapılmasını isteyebiliriz. Bunun en iyi örneği, bir dosya indirme programı yazdığımızda dosyalar inerken programın donmaması, indirme işleminin iptal edilebilmesidir. Aynı şekilde, hesaplama yaparken ana programı sonlandırmadan bu hesabı durdurabilmeyi, devam ettirebilmeyi veya baştan başlatabilmeyi de isteriz. Başka bir örnekse, bir müzik çalacak programda müzik çalarken müziğin kontrol edilebilmesi, ileri-geri sarabilme ve diğer menüleri kontrol edebilme özellikleridir.
Şimdi bu işlemi nasıl yapabileceğimize bakalım. Öncelikle, bu işlem için threading isimli modülden faydalanıyoruz. Bu modülün içerisindeki Thread isimli sınıfta işimize yarayacak fonksiyon bulunuyor. Normalde class yapısını kullanmadan ve kullanarak 2 şekilde yapılan bu işlem için daha anlaşılır olduğunu düşündüğümden dolayı class yapısını kullanarak örnek vereceğim. Önce modülü import edelim:
import threading
Modülümüzü import ettik. Şimdi dikkat etmemiz gereken noktaya geliyoruz. Arkaplanda çalıştırmak istediğimiz fonksiyonları içeren bir sınıf tanımlayacağız. Sınıfımızın adı işlem olsun. Python3 te çalıştığımızı ve bu yüzden Türkçe karakter içeren değişken isimleri kullandığımızı da belirtelim. Şimdi, bu sınıf içerisine, eğer arkaplanda çalışacak fonksiyonlara göndermek istediğimiz parametre varsa, yani sınıfı örneklerken vermek istediğimiz parametreler mevcutsa, init fonksiyonunu da tanımlamamız gerekiyor. Eğer sadece işlemi başlatmak istiyorsak, mesela görsel bir programda kullanıcının girdiği verileri get methoduyla alacaksak bunu yapmamız gerekmiyor, dolayısıyla miras alınan init fonksiyonunu kullanıyoruz. İşlem adlı sınıfta init fonksiyonunu da oluşturalım. Dediğim gibi, parametre göndermeyeceksek bu sınıfı yazmamız gerekmiyor. Programımız bir olasılık dağılımı denemesi yapsın(yanlış hatırlamıyorsam Gauss dağılımı). 10 tane parayı havaya attığımızda gelecek olan yazı sayısının 5 olma olasılığının 1⁄2 olduğunu gösterelim. Biliyoruz ki, örnek sayısı arttıkça dağılım 5.00 a daha fazla yaklaşıyor. bunun için 1000000000 tane deneme yapıp toplamı bulduralım. Lafı fazla uzatmadan, programımızın kodlarını görelim ve daha sonra açıklayalım:
import threading, random
class işlem(threading.Thread):
def __init__(self):
# Sınıfı örneklerken almak istediğimiz argümanları burada atıyoruz, init fonksiyonuna da parametreleri ekliyoruz.
threading.Thread.__init__(self)
def run(self):
self.liste = list(range(0,11))
self.çıkış = 0
self.toplam = 0
self.kaçtane = 0
for i in range(1000):
for i in range(1000000):
if self.çıkış == 1:
break
self.kaçtane +=1
self.toplam += random.choice(self.liste)
if self.çıkış == 1:
print("Toplam hesaplanan: {}, toplamları: {}, ortalama: {} \n"\
.format(self.kaçtane,self.toplam,self.toplam/self.kaçtane))
break
print("Toplam hesaplanan: {}, toplamları: {}, ortalama: {} \n"\
.format(self.kaçtane,self.toplam,self.toplam/self.kaçtane))
return True
çalıştır = işlem()
while True:
gelen = input("Çalıştırmak için 1, durdurup toplamı görmek için 0\
, programı kapamak için 2 giriniz.")
try:
if int(gelen) == 1:
çalıştır.start()
print("Program çalışıyor...\n")
elif int(gelen) == 0:
çalıştır.çıkış = 1
elif int(gelen) == 2:
break
except:
print("Yanlış bir giriş yaptınız, tekrar deneyin...\n")
İlk satırda, çoklu işlem yapmamızı sağlayan threading modülünü ve rastgele sayılar seçmemize yarayacak olan random modülünü aldık. Hemen ardından 3. satırda işlem adlı bir sınıf oluşturduk. Bu sınıf threading modülünde bulunan threading.Thread sınıfını miras alıyor. Yani bu sınıfın içerisinde bizim çoklu işlem yapmamızı sağlayan nitelikler mevcut ve biz bunu işlem adlı sınıfa taşıdık. Daha sonra da kendi fonksiyonlarımızı ekleyip bunları kullanacağız.
İşlem sınıfının başlangıç fonksiyonunu burada işimize yaramayacak da olsa diğer yerlerde lazım olabileceği düşüncesiyle oluşturduk(4. satır) ve 6. satırda threading modülünün init fonksiyonunu da kendi init() fonksiyonumuza dahil ettik. Threading.thread sınıfının init fonksiyonunun adının threading.Thread.init(self) şeklinde kullanılarak bu fonksiyonun çağırıldığına dikkat etmek gerekiyor. Aksi takdirde çoklu işlemde sorun yaşayabiliriz. Bu arada, oradaki yorum satırında da belirttiğim üzere sınıfı örneklerken göndermek istediğimiz değişken varsa orda self.değişken şeklinde atamasını yapabiliriz. Tabii ki ilk olarak kendi tanımladığımız init fonksiyonuna parametrelerini eklememiz gerekiyor.
run() fonksiyonuna dikkat etmemiz gerekiyor. Bu ismi biz vermedik. Bu modülü kullanacak ve bu sınıf yapısını uygulayacaksak run() fonksiyonunun orda olması gerekiyor. Onun içerisinden istediğimiz başka fonksiyonları çağırabilir, ya da bütün işlemleri ona da yaptırabiliriz. Ama dediğim gibi, run fonksiyonu bu isimle orada yer almalı. Çünkü işlemi birazdan başlattığımızda threading modülü run fonksiyonunu arayacak.
Şimdi run() fonksinonunun içerisinde ne yaptığımıza bakalım. Öncelikle, her defasında yeniden 0’dan 10’a kadar olan sayıları tanımlayıp programı verimsizleştirmek yerine bir seferde tanımlamayı seçtik. Bu arada belirteyim, random modülünün random.randint() isimli fonksiyonu istediğimiz aralıkta rastgele tamsayı seçmeye yarıyor. Hangisinin daha verimli olduğunu test etmedim, bir ara eder sonuçlarını paylaşırım. Şimdi işimize dönelim, sınıfın liste adlı değişkenine range fonksiyonula 0-10 dahil olmak üzere bu sayıların aralarındaki tamsayıları atadık. Yani 0-1-2-…-10 şeklinde bir listemiz oldu. Python2 den farklı olarak bu sayıları atarken range fonksiyonundan gelen sonucu list() fonksiyonuna dahil ettiğimize dikkat edin.
Tabii burda toplamı ve kaç sayı hesapladığını göstermemiz için de bu değerleri tutan değişkenlere ihtiyacımız var. Bunu da 10 ve 11. satırlarda toplam kaç tane rastgele sayı seçildiğini tutan değişkenimiz olan self.kaçtane ve bu sayıların toplamını tutan değişkenimiz olan self.toplam ile sağladık.
Artık elimizde kaç tane yazı veya tura seçildiğini gösterebileceğimiz bir listemiz var. Listeden rastgele eleman seçmeye başlamadan önce, bu işlemi 1 milyar kez tekrarlamak için gerekli kodları yazalım. Öncelikle for döngüsünü istediğimiz sayıda döndürürken kullandığımız range fonksiyonu bu iş için de bir vazgeçilmezimiz. Ancak range fonksiyonu 1000000000 sayısında hata verdiği için iç içe 2 for döngüsü kullanarak 1 milyon tane sayıyı 1000kere seçmesini istedik. Böylece elimizde 1 milyar tane sayı oldu.
Şimdi biraz önce de bahsettiğimiz if yapısına geçelim. İlk olarak 14. satırda gördüğümüz if yapısı, dışarıdan self.çıkış değişkeninin değeri 1 yapılırsa döngüyü sonlandıracak, daha sonra bir üst for döngüsü de sonlanacak ve o ana kadar kaç sayı geldiyse onları, toplamlarını ve ortalamasını yazacak.
Şimdi 16 ve 17. satırlara bakalım. 16. satırda o ana kadar kaç sayı hesapladığımızı tutan self.kaçtane isimli değişken artırılıyor. Tahmin edeceğiniz üzere bu değişkeni her döngüde 1 artırıyoruz. Hemen ardından 0 ile 10 kapalı aralığından random.choice() ile daha önce range() ile yaptığımız atamadan elde ettiğimiz self.liste isimli listeden rastgele bir sayı seçiyoruz ve bu sayıyı self.toplam adlı değişkene ekliyoruz. Böylece elimizde toplam değeri tutan self.toplam değişkeni işlevini yerine getirmiş oluyor.
18-21. satırlar arasında ise yukarıda bahsetttiğimiz self.çıkış isimli değişken 1 yapıldıysa, yani programa çıkması komutu verildiyse çıkması için, çıkarken de o ana kadar kaç sayı hesaplandıysa bunların sayısını, toplamını ve ortalamasını hesaplaması için gerekli kodları yazdık. Bunlarda sıkıntınız olursa sorabilirsiniz.
22 ve 23. satırlar yukarıda belirttiğimiz işlemi döngüler bitene kadar durdurulmazsa yapıyor. Yani 1 milyar sayı hesaplanırsa sonuçları gösteriyor.
Bu konuyu elimden geldiğince detaylı açıklamaya çalıştım. Aklınıza takılan bir şey olursa sormaktan çekinmeyin.