Konuları Senkronize Etme
İş parçacığı senkronizasyonu, iki veya daha fazla eşzamanlı iş parçacığının aynı anda kritik bölüm olarak bilinen program bölümüne erişmediğinden emin olabileceğimiz bir yöntem olarak tanımlanabilir. Öte yandan, kritik bölümün programın paylaşılan kaynağa erişilen bölümü olduğunu biliyoruz. Dolayısıyla senkronizasyonun, kaynaklara aynı anda erişerek iki veya daha fazla iş parçacığının birbiriyle arayüz oluşturmamasını sağlama işlemi olduğunu söyleyebiliriz. Aşağıdaki diyagram, bir programın kritik bölümüne aynı anda erişmeye çalışan dört iş parçacığını göstermektedir.
Daha net hale getirmek için, iki veya daha fazla iş parçacığının aynı anda listeye nesneyi eklemeye çalıştığını varsayalım. Bu eylem, nesnelerden birini veya tümünü bırakacağı veya listenin durumunu tamamen bozacağı için başarılı bir sona götürmez. Burada senkronizasyonun rolü, bir seferde yalnızca bir iş parçacığının listeye erişebilmesidir.
İş parçacığı senkronizasyonunda sorunlar
Eşzamanlı programlama uygularken veya senkronize ilkelleri uygularken sorunlarla karşılaşabiliriz. Bu bölümde iki ana konuyu tartışacağız. Sorunlar -
- Deadlock
- Yarış kondisyonu
Yarış kondisyonu
Bu, eşzamanlı programlamadaki en önemli sorunlardan biridir. Paylaşılan kaynaklara eşzamanlı erişim, yarış durumuna neden olabilir. Bir yarış koşulu, iki veya daha fazla iş parçacığının paylaşılan verilere erişebildiği ve ardından aynı anda değerini değiştirmeye çalıştığı bir koşulun meydana gelmesi olarak tanımlanabilir. Bundan dolayı, değişkenlerin değerleri öngörülemez olabilir ve süreçlerin bağlam anahtarlarının zamanlamalarına bağlı olarak değişebilir.
Misal
Yarış durumu kavramını anlamak için bu örneği düşünün -
Step 1 - Bu adımda, iş parçacığı modülünü içe aktarmamız gerekiyor -
import threading
Step 2 - Şimdi, 0 değeriyle birlikte x gibi bir küresel değişken tanımlayın -
x = 0
Step 3 - Şimdi, tanımlamamız gerekiyor increment_global() fonksiyon, bu global fonksiyonda 1 artış yapacak olan x -
def increment_global():
global x
x += 1
Step 4 - Bu adımda, taskofThread()işlev, belirtilen sayıda artış için increment_global () işlevini çağırır; örneğimiz için 50000 kere -
def taskofThread():
for _ in range(50000):
increment_global()
Step 5- Şimdi, t1 ve t2 evrelerinin oluşturulduğu main () işlevini tanımlayın. Her ikisi de start () işlevinin yardımıyla başlatılacak ve join () işlevi yardımıyla işlerini bitirene kadar bekleyecekler.
def main():
global x
x = 0
t1 = threading.Thread(target= taskofThread)
t2 = threading.Thread(target= taskofThread)
t1.start()
t2.start()
t1.join()
t2.join()
Step 6- Şimdi, main () işlevini çağırmak istediğimiz kaç yineleme için aralığı vermemiz gerekiyor. Burada 5 defa arıyoruz.
if __name__ == "__main__":
for i in range(5):
main()
print("x = {1} after Iteration {0}".format(i,x))
Aşağıda gösterilen çıktıda, her yinelemeden sonra 100000 beklendiği için x'in değeri olarak yarış koşulunun etkisini görebiliriz. Ancak, değerde çok fazla varyasyon vardır. Bunun nedeni, iş parçacıklarının paylaşılan global değişken x'e eşzamanlı erişimidir.
Çıktı
x = 100000 after Iteration 0
x = 54034 after Iteration 1
x = 80230 after Iteration 2
x = 93602 after Iteration 3
x = 93289 after Iteration 4
Kilitleri kullanarak yarış durumu ile başa çıkmak
Yukarıdaki programda yarış koşulunun etkisini gördüğümüz gibi, birden fazla iş parçacığı arasındaki yarış koşuluyla başa çıkabilen bir senkronizasyon aracına ihtiyacımız var. Python'da<threading>modül, yarış koşullarıyla başa çıkmak için Kilit sınıfını sağlar. Dahası,Locksınıfı, birden çok iş parçacığı arasındaki yarış durumunu ele alabileceğimiz farklı yöntemler sağlar. Yöntemler aşağıda açıklanmıştır -
acquire () yöntemi
Bu yöntem, bir kilidi elde etmek, yani bloke etmek için kullanılır. Bir kilit, aşağıdaki doğru veya yanlış değere bağlı olarak bloke edici olabilir veya olmayabilir -
With value set to True - accire () yöntemi, varsayılan argüman olan True ile çağrılırsa, kilit kilidi açılana kadar iş parçacığı yürütmesi engellenir.
With value set to False - accire () yöntemi, varsayılan bağımsız değişken olmayan False ile çağrılırsa, iş parçacığı yürütmesi true olarak ayarlanıncaya, yani kilitlenene kadar engellenmez.
release () yöntemi
Bu yöntem bir kilidi serbest bırakmak için kullanılır. Aşağıda, bu yöntemle ilgili birkaç önemli görev verilmiştir -
Bir kilit kilitliyse, release()yöntem onun kilidini açar. İşi, birden fazla iş parçacığı bloke edilmişse ve kilidin kilidinin açılmasını beklemesi durumunda tam olarak bir iş parçacığının ilerlemesine izin vermektir.
Bir ThreadError kilit zaten açıksa.
Şimdi, yarış durumundan kaçınmak için yukarıdaki programı kilit sınıfı ve yöntemleri ile yeniden yazabiliriz. TaskofThread () yöntemini kilit bağımsız değişkeniyle tanımlamamız ve ardından yarış durumundan kaçınmak için kilitlerin engellenmesi ve engellenmemesi için acquire () ve release () yöntemlerini kullanmamız gerekir.
Misal
Aşağıda, yarış durumu ile başa çıkmak için kilit kavramını anlamak için bir python programı örneği verilmiştir -
import threading
x = 0
def increment_global():
global x
x += 1
def taskofThread(lock):
for _ in range(50000):
lock.acquire()
increment_global()
lock.release()
def main():
global x
x = 0
lock = threading.Lock()
t1 = threading.Thread(target = taskofThread, args = (lock,))
t2 = threading.Thread(target = taskofThread, args = (lock,))
t1.start()
t2.start()
t1.join()
t2.join()
if __name__ == "__main__":
for i in range(5):
main()
print("x = {1} after Iteration {0}".format(i,x))
Aşağıdaki çıktı yarış durumunun etkisinin ihmal edildiğini göstermektedir; x'in değeri, her ve her yinelemeden sonra, şimdi 100000'dir, bu da bu programın beklentisine göre.
Çıktı
x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4
Çıkmazlar - Yemek Filozofları sorunu
Kilitlenme, eşzamanlı sistemleri tasarlarken karşılaşılabilecek zahmetli bir sorundur. Bu konuyu yemek filozofu probleminin yardımıyla şu şekilde açıklayabiliriz:
Edsger Dijkstra, ilk olarak, eşzamanlı sistemin en büyük sorunlarından biri olan çıkmazdan birinin ünlü örneklerinden biri olan yemek filozofu sorununu tanıttı.
Bu problemde yuvarlak bir masada oturup kaselerinden yemek yiyen beş ünlü filozof var. Beş filozofun yemeklerini yemek için kullanabileceği beş çatal vardır. Ancak filozoflar yemeklerini yemek için aynı anda iki çatal kullanmaya karar verirler.
Şimdi filozoflar için iki temel koşul var. Birincisi, filozofların her biri ya yemek yerken ya da düşünme durumunda olabilir ve ikincisi, önce her iki çatalları, yani sol ve sağ almaları gerekir. Sorun, beş filozofun her biri aynı anda sol çatalı seçmeyi başardığında ortaya çıkar. Şimdi hepsi doğru çatalın bedava olmasını bekliyor, ancak yiyeceklerini yiyene ve doğru çatal asla bulunmayana kadar çatallarını asla bırakmayacaklar. Bu nedenle, yemek masasında bir kilitlenme durumu olacaktır.
Eşzamanlı sistemde kilitlenme
Şimdi görürsek, aynı sorun eşzamanlı sistemlerimizde de ortaya çıkabilir. Yukarıdaki örnekteki çatallar, sistem kaynakları olabilir ve her filozof, kaynakları elde etmek için yarışan süreci temsil edebilir.
Python programı ile çözüm
Bu sorunun çözümü, filozofları iki türe ayırarak bulunabilir: greedy philosophers ve generous philosophers. Çoğunlukla açgözlü bir filozof, sol çatalı kaldırmaya çalışacak ve orada olana kadar bekleyecektir. Daha sonra doğru çatalın orada olmasını bekleyecek, alacak, yiyecek ve sonra yere koyacaktır. Öte yandan, cömert bir filozof sol çatalı kaldırmaya çalışacak ve orada değilse bir süre sonra bekleyip tekrar deneyecektir. Sol çatalı alırlarsa, doğru olanı almaya çalışacaklardır. Doğru çatalı da alacaklarsa, her iki çatalı da yiyip bırakacaklar. Ancak, sağ çatalı alamazlarsa, sol çatalı serbest bırakırlar.
Misal
Aşağıdaki Python programı, yemek filozofu sorununa bir çözüm bulmamıza yardımcı olacak -
import threading
import random
import time
class DiningPhilosopher(threading.Thread):
running = True
def __init__(self, xname, Leftfork, Rightfork):
threading.Thread.__init__(self)
self.name = xname
self.Leftfork = Leftfork
self.Rightfork = Rightfork
def run(self):
while(self.running):
time.sleep( random.uniform(3,13))
print ('%s is hungry.' % self.name)
self.dine()
def dine(self):
fork1, fork2 = self.Leftfork, self.Rightfork
while self.running:
fork1.acquire(True)
locked = fork2.acquire(False)
if locked: break
fork1.release()
print ('%s swaps forks' % self.name)
fork1, fork2 = fork2, fork1
else:
return
self.dining()
fork2.release()
fork1.release()
def dining(self):
print ('%s starts eating '% self.name)
time.sleep(random.uniform(1,10))
print ('%s finishes eating and now thinking.' % self.name)
def Dining_Philosophers():
forks = [threading.Lock() for n in range(5)]
philosopherNames = ('1st','2nd','3rd','4th', '5th')
philosophers= [DiningPhilosopher(philosopherNames[i], forks[i%5], forks[(i+1)%5]) \
for i in range(5)]
random.seed()
DiningPhilosopher.running = True
for p in philosophers: p.start()
time.sleep(30)
DiningPhilosopher.running = False
print (" It is finishing.")
Dining_Philosophers()
Yukarıdaki program, açgözlü ve cömert filozoflar kavramını kullanır. Program ayrıcaacquire() ve release() yöntemleri Lock sınıfı <threading>modül. Çözümü aşağıdaki çıktıda görebiliriz -
Çıktı
4th is hungry.
4th starts eating
1st is hungry.
1st starts eating
2nd is hungry.
5th is hungry.
3rd is hungry.
1st finishes eating and now thinking.3rd swaps forks
2nd starts eating
4th finishes eating and now thinking.
3rd swaps forks5th starts eating
5th finishes eating and now thinking.
4th is hungry.
4th starts eating
2nd finishes eating and now thinking.
3rd swaps forks
1st is hungry.
1st starts eating
4th finishes eating and now thinking.
3rd starts eating
5th is hungry.
5th swaps forks
1st finishes eating and now thinking.
5th starts eating
2nd is hungry.
2nd swaps forks
4th is hungry.
5th finishes eating and now thinking.
3rd finishes eating and now thinking.
2nd starts eating 4th starts eating
It is finishing.