Kurs
Eğer Python’a yeni başladıysanız ve daha fazlasını öğrenmek istiyorsanız, DataCamp’in Python ile Veri Bilimine Giriş kursunu alın.

Giriş
Threading, sürecinizin farklı bölümlerinin eşzamanlı olarak çalışmasına olanak tanır (Kaynak: RealPython). Bu farklı bölümler genellikle bağımsızdır ve aynı sürece ait ayrı bir yürütme birimine sahiptir. Süreç, eşzamanlı çalıştırılabilen bireysel birimlere sahip çalışan bir programdan ibarettir. Örneğin, bir web tarayıcısı bir süreç olabilir; aynı anda birden fazla kamerayı çalıştıran bir uygulama bir süreç olabilir; bir video oyunu da başka bir süreç örneğidir.
Bir sürecin içinde, çoklu threading (çok iş parçacığı) kavramı gelir; burada birden fazla iş parçacığı ortak bir hedefe ulaşmak için birlikte çalışır. Thread kullanmanın en temel faydası, programı paralel olarak çalıştırmanıza olanak sağlamasıdır.
Threading kavramını bir örnekle anlayalım. AVM’nin otoparkına giren ve çıkan arabaların sayısını sayan bir uygulamanız olduğunu hayal edin. Düzeneğiniz, giriş ve çıkışı izleyen ve merkezi bir cihaza bağlanan çeşitli kameralara sahiptir. Her kamera, araç akışını izleyen bir algoritmaya sahip olacaktır; bu algoritmalar aynı süreç ya da programa aittir. Ancak her kamera, üzerinde çalıştırılan algoritmayla birlikte ayrı bir iş parçacığının parçası olabilir. Hatta, kameradan okunan kareler ile bu kareler üzerinde tahmin yapan algoritma dahi iki ayrı iş parçacığı olabilir.
Bir başka örnek de bir video oyunudur; süreç, grafikler, kullanıcı etkileşimi ve ağ iletişimi (çok oyunculu oyunlarda) gibi görevleri her zaman duyarlı olabilmek için paralel çalıştırmak zorundadır. Bunu başarmak için, her iş parçacığının bağımsız bir görevi yürütmekten sorumlu olduğu çoklu threading kavramından yararlanır.
Bir iş parçacığının kendi yürütme akışı vardır; bu da süreçte aynı anda birden fazla şeyin gerçekleştiği anlamına gelir.
Her sürecin en az bir iş parçacığı olduğunu ve bunun main thread (ana iş parçacığı) olarak adlandırıldığını not etmek önemlidir. Programınızda tanımlı herhangi bir iş parçacığı yoksa, en azından ana iş parçacığı, yani bizzat sürecin kendisi olacaktır.
Yukarıdaki diyagrama bakarsanız, genel değişkenlerden oluşan bir ana iş parçacığı ve t1, t2, t3 adlı üç farklı iş parçacığı içeren bir süreç görürsünüz. Her iş parçacığının kendi yerel değişkenleri ve kod yürütme akışı vardır. İş parçacıkları genellikle sürecin kaynaklarını paylaşır; yani ana iş parçacığına açık olan kaynaklar t1, t2 ve t3 arasında da paylaşılır. Tüm iş parçacıkları, kendi yerel değişkenlerine sahip olurken genel değişkenlere erişebilir.
Farklı türde iş parçacıkları vardır:
- Çekirdek (kernel) iş parçacığı
- Kullanıcı iş parçacığı
- Çekirdek ve kullanıcı iş parçacığı kombinasyonu
Şimdi, programınızda threading kullanmanın bazı avantajlarına bakalım:
-
Çoklu iş parçacığı, birden çok CPU olduğu sürece programın yürütmesini hızlandırabilir.
-
Ayrıca, birden çok iş parçacığının ya da ana iş parçacığı ile tek bir iş parçacığının yardımıyla, G/Ç (I/O) işlemleri yürütülürken diğer görevleri gerçekleştirmenize olanak tanır. Örneğin, kameradan karelerin okunma hızı ile algoritma tarafından çıkarım yapılma hızı farklı iş parçacıkları tarafından ele alınacaktır. Böylece algoritma, bir kare girişini beklemek zorunda kalmaz ve kare okuma kısmı da bir sonraki kareyi okuyabilmek için algoritmanın yürütmesinin tamamlanmasını beklemek zorunda kalmaz.
- Aynı süreç içindeki iş parçacıkları, ana iş parçacığının bellek ve kaynaklarını paylaşabilir.
Threading’in Zorlukları
-
Python, CPython uygulamasına dayalı çalışır ve bu, aynı anda yalnızca bir iş parçacığının çalıştırılmasını sınırlar; dolayısıyla threading her görevi hızlandırmayabilir. Bunun temel nedeni Global Interpreter Lock’tur (GIL).
GIL hakkında bilgi edinmek isterseniz, bu eğitime göz atın.
-
CPU yoğun görevleri hızlandırmak istiyorsanız, threading sizin için en iyi çözüm olmayabilir. Bu tür durumlarda çoklu işleme (multi-processing) yararlı kabul edilir.
-
Çeşitli iş parçacıklarından oluşan bir programı yürütmek için iş parçacıkları arasında hızlıca geçiş yapılması gerekir; bu nedenle çok sayıda iş parçacığı olan programlarda zamanlamanın (scheduling) dikkatle ele alınması gerekir.
-
Kaynak paylaşımı da bir sorun olabilir; çünkü tüm iş parçacıkları aynı kaynakları ve genel değişkenlerin belleğini paylaşır. Bu nedenle, bir iş parçacığında gerçekleştirilen işlemler başka bir iş parçacığı için bellek hatasına neden olabilir ya da diğer iş parçacığı görevini gerçekleştirmek için gerekli belleği alamayabilir.
Python’da Threading
-
Python’da threading modülü,
threadingadıyla yerleşik bir modüldür ve doğrudan içe aktarılabilir. -
Python’da neredeyse her şey bir nesne olarak temsil edildiğinden, threading de Python’da bir nesnedir. Bir iş parçacığı şunları yapabilir:
- Veri tutmak,
- Sözlükler, listeler, kümeler vb. veri yapılarında saklanmak,
- Bir fonksiyona parametre olarak geçirilmek.
-
Bir iş parçacığı bir süreç olarak da yürütülebilir.
-
Python’da bir iş parçacığının aşağıdaki gibi çeşitli durumları olabilir:
- Bekleme,
- Kilitli.
Şimdi Python’da threading’i nasıl uygulayabileceğinizi öğrenelim.
Python3’te Thread modülü
Python3’ün, Python2.7’de bulunan thread modülüyle geriye dönük uyumlu olduğunu unutmayın. Python3’te bu modül _thread olarak içe aktarılabilir. Bir örnek üzerinden _thread modülünü anlayalım.
import _thread #thread module imported
import time #time module
thread_delay adlı bir fonksiyon tanımlayalım; bu fonksiyon, giriş olarak iki parametre alacak: iş parçacığının adı ve gecikme süresi. Bu fonksiyon içinde şunları yapacaksınız:
- Sıfırdan başlayan bir sayaç tanımlayın,
- Üç kez dönecek bir while döngüsü kurun,
- While döngüsü içinde, gecikme eklemek için
time.sleep()kullanın; bu gecikme, iş parçacığının yürütülmesini anlamanızı sağlayacaktır. Gecikme saniye cinsindendir, - Ardından sayacı 1 artırın,
- İş parçacığı yürütmenizin düzgün gidip gitmediğini görmek için iş parçacığının adını ve yürütüldüğü zamanı yazdırın.
def thread_delay(thread_name, delay):
count = 0
while count < 3:
time.sleep(delay)
count += 1
print(thread_name, '-------->', time.time())
Şimdi, yukarıdaki fonksiyona iş parçacığı işlevselliği eklemek ya da bu fonksiyonu bir iş parçacığında çalıştırmak için, _thread module içinde yer alan start_new_thread metodunu kullanacaksınız.
start_new_thread metodunun docstring’ine bakalım.
?_thread.start_new_thread

Fonksiyon thread_delay’i parantezsiz ve iki argümanla (iş parçacığı adı ve gecikme — iş parçacığı yürütmesini görselleştirmek için çünkü çok hızlıdır) geçirelim.
_thread.start_new_thread(thread_delay, ('t1', 1))
_thread.start_new_thread(thread_delay, ('t2', 3))
10916
t1 --------> 1588077856.4221454
t1 --------> 1588077857.4418247
t2 --------> 1588077858.422453
t1 --------> 1588077858.4431
t2 --------> 1588077861.4371448
t2 --------> 1588077864.438915
Yukarıdaki çıktıda, önce t1 iş parçacığının yürütülmeye başladığını görebilirsiniz. Bu sırada t2 iş parçacığı 3 saniyelik gecikme nedeniyle bekler ve bu gecikme biter bitmez t2 yürütülür; ayrıca t1 için gecikme sadece 1 saniyedir.
Şimdi t2’nin gecikmesini 5 saniyeye değiştirelim; böylece t2, t1 yürütmesini bitirdikten sonra başlayacaktır çünkü t1 yürütmesini tamamlamak için yalnızca 3 saniye alacaktır.
_thread.start_new_thread(thread_delay, ('t1', 1))
_thread.start_new_thread(thread_delay, ('t2', 5))
14632
t1 --------> 1588078001.4087512
t1 --------> 1588078002.4272866
t1 --------> 1588078003.432218
t2 --------> 1588078005.4082909
t2 --------> 1588078010.4268317
t2 --------> 1588078015.4283602
Threading modülünü kullanarak threading uygulama
Yukarıda kullandığınız aynı örneği kullanalım; ancak bu kez _thread modülü yerine threading modülünü kullanacaksınız.
import threading
import time
def thread_delay(thread_name, delay):
count = 0
while count < 3:
time.sleep(delay)
count += 1
print(thread_name, '-------->', time.time())
threading modülünün içinde, _thread modülündeki start_new_thread fonksiyonu ile benzer ruha sahip bir Thread sınıfı vardır.
Grup, hedef (bir fonksiyon gibi), args vb. çeşitli parametreler alan Thread sınıfının docstring’ine bakalım.
?threading.Thread

Thread sınıfının kurucusuna, hedef fonksiyon olarak thread_delay’i ve o fonksiyonun argümanlarını geçeceksiniz.
t1 = threading.Thread(target=thread_delay, args=('t1', 1))
t2 = threading.Thread(target=thread_delay, args=('t2', 3))
Threading modülünde bir iş parçacığını çalıştırmak/yürütmek için, iş parçacığını çalıştırmaktan sorumlu start() metodunu kullanırsınız.
?t1.start

t1.start()
t2.start()
t1 --------> 1588081121.7790291
t1 --------> 1588081122.7962046
t2 --------> 1588081123.7789369
t1 --------> 1588081123.7980876
t2 --------> 1588081126.7961557
t2 --------> 1588081129.7995002
Ayrıca, tüm iş parçacıklarının yürütmesi tamamlanana kadar beklemek anlamına gelen join metodunu da kullanacaksınız. Yani join metodundan sonra yazdığınız herhangi bir kod, bu iş parçacıkları sonlandırıldıktan sonra çalışacaktır.
?t1.join

t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
t1 --------> 1588081798.7118263
t1 --------> 1588081799.7231226
t2 --------> 1588081800.7129223
t1 --------> 1588081800.730732
t2 --------> 1588081803.7298765
t2 --------> 1588081806.7309563
Thread execution is complete!
Şimdi threading kavramını kullanarak küp ve kare hacmini hesaplayalım; oldukça basit.
def volume_cube(a):
print ("Volume of Cube:", a*a*a)
def volume_square(a):
print ("Volume of Square:", a*a)
t1 = threading.Thread(target=volume_cube, args=(2))
t2 = threading.Thread(target=volume_square, args=(3))
t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
Thread execution is complete!
Exception in thread Thread-9:
Traceback (most recent call last):
File "C:\Program Files\Anaconda3\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Program Files\Anaconda3\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
TypeError: volume_square() argument after * must be an iterable, not int
Exception in thread Thread-8:
Traceback (most recent call last):
File "C:\Program Files\Anaconda3\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Program Files\Anaconda3\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
TypeError: volume_cube() argument after * must be an iterable, not int
Bir dakika, ne? start metodunu çalıştırır çalıştırmaz hata oluştu. Bunun nedeni, args parametresinin bir demet (tuple) beklemesi ve volume_cube ile volume_square fonksiyonlarının yalnızca birer parametre beklemesi; dolayısıyla fonksiyonların ilk argümanını belirttikten sonra args parametresinde bir virgül koymanız gerekir.
t1 = threading.Thread(target=volume_cube, args=(2,))
t2 = threading.Thread(target=volume_square, args=(3,))
t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
Volume of Cube: 8
Volume of Square: 9
Thread execution is complete!
Şimdi threading’i bir alt sınıf olarak kullanmayı öğrenelim.
Alt sınıf olarak Threading
Bu bölümde, threading modülünde yer alan iş parçacığı sınıfından nasıl alt sınıf oluşturacağınızı ve ardından bu sınıftan t1 ve t2 iş parçacığı örnekleri yaratacağınızı öğreneceksiniz.
Aynı thread_delay fonksiyonunu burada da kullanacaksınız.
class DataCampThread(threading.Thread):
def __init__(self, name, delay):
threading.Thread.__init__(self)
self.name = name
self.delay = delay
def run(self):
print('Starting Thread:', self.name)
thread_delay(self.name,self.delay)
print('Execution of Thread:', self.name, 'is complete!')
t1 = DataCampThread('t1', 1)
t2 = DataCampThread('t2', 3)
t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
Starting Thread: t1
Starting Thread: t2
t1 --------> 1588089083.4879806
t1 --------> 1588089084.489222
t1 --------> 1588089085.4909666
Execution of Thread: t1 is complete!
t2 --------> 1588089085.4979634
t2 --------> 1588089088.499365
t2 --------> 1588089091.5012596
Execution of Thread: t2 is complete!
Thread execution is complete!
Şimdi yukarıdaki kodu adım adım anlayalım:
-
DataCampThreadadlı bir sınıf tanımladınız ve sınıf argümanındathreading.Threadsınıfını geçtiniz. Bunu yapmanızın nedeni, threading modülünden bir alt sınıf oluşturmak istemenizdir. Bunu yapmak, kalıtım kavramına benzer şekildethreading.Threadsınıfında mevcut tüm metotları kullanmanıza olanak tanır. -
Sonra DataCampThread sınıfının init metodunu tanımladınız ve fonksiyonu gecikme ile birlikte geçtiniz. Init,
threading.Threadsınıfına geçirdiğiniz parametrelere benzer bir kurucudur; burada iseDataCampThreadsınıfına geçirirsiniz. -
Daha sonra ana threading.Thread sınıfının init metodunu başlatır ve name ile delay’i atarsınız.
-
Ardından
runmetodunu tanımlarsınız; burada tanımladığınızstartmetodunun, kaputun altında aslındarunmetodunu çağırdığını hatırlayın. Ana sınıfı miras aldığınız içinrunmetodunda değişiklik yapmanıza izin verilir. Dolayısıylastartmetodunu çağırdığınızda,runmetodunda yaptığınız değişiklikler şimdi yansıtılacaktır.
Sonuç
Eğitimi tamamladığınız için tebrikler.
Bu eğitim, Python’da threading’e temel bir girişti. Ancak, threading çok geniş ve hayati bir konudur ve bazı kavramlar kapsam dışında kalmış olabilir. active count, current thread gibi daha fazla threading metoduna bakmaktan çekinmeyin.
Daha fazlasını öğrenmek isterseniz, Python’un İş Parçacığı Tabanlı Paralellik belgesine de bakın.
Python’a yeni başlıyorsanız ve daha fazlasını öğrenmek istiyorsanız, DataCamp’in Python ile Veri Bilimine Giriş kursunu alın.
