Python ile Aynı Anda Birden Çok İşlem (Multithreading)

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 12 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.

  1. işlemi istediğimiz zaman sonlandırabilmek için yine işlem adlı sınıfımızaçıkışisimli bir değişken ekledik(satır 9). Bu değişkenin değerine programımızın herhangi bir yerinde(tabii sınıfı örnekledikten sonra) ulaşıp onu 1 yaparsak rastgele sayılar seçmeyi bırakıp bize o ana kadar kaç tane seçtiyse o sayıyı, toplamlarını ve elde ettiği sonucu göstermesini istiyoruz. Bunları birazdan if yapısı ile halledeceğiz.

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.

  1. satırda olan return True ifadesine pek takılmaya gerek yok, işlem bitince saf saf beklememesi için ekledim, sanırım pek bir işe yaramadı.

  2. satırda elimizdeki sınıfı örnekledik. Bu da multithreading ile ilgili bir konu. Zira bunu yapmamız gerekiyor. Daha sonra birazdan da göreceğimiz gibi sınıfı örneklediğimiz değişkenin start() methodunu kullanarak arkaplan işlemini başlatacağız. Burada çalıştır adlı değişkene sınıfımızı örnekledik.

  3. satırda programı sonsuz döngüye sokarak kullanıcı ile etkileşimin kesilmemesini sağladık. Kullanıcı istediği zaman sınıfın çıkış değişkenini 1 yapabilsin ki üstteki if yapısından döngüyü durdurup sonuçlarını görebilsin istedik. Ayrıca döngüleri başlatmak için biraz önce söylediğimiz start fonksiyonunu da burada kullanacağız.  Kalan kısım zaten basit bir menü tasarımı oldu. Kullanıcı 1’e basarsa çalıştır.start() komutuyla programı başlattık.  0’a basarsa durdurup görmesini sağladık ve 2’ye basarsa sonsuz döngüyü sonlandırıp programın kapanmasını sağladık.

Bu konuyu elimden geldiğince detaylı açıklamaya çalıştım. Aklınıza takılan bir şey olursa sormaktan çekinmeyin.

 
comments powered by Disqus