Konular İletişim
Gerçek hayatta, eğer bir ekip ortak bir görev üzerinde çalışıyorsa, o zaman görevi düzgün bir şekilde bitirmek için aralarında iletişim olmalıdır. Aynı benzetme iplikler için de geçerlidir. Programlamada, işlemcinin ideal süresini azaltmak için birden çok iş parçacığı oluşturur ve her iş parçacığına farklı alt görevler atarız. Bu nedenle, bir iletişim aracı olmalı ve işi senkronize bir şekilde bitirmek için birbirleriyle etkileşime girmelidirler.
İş parçacığı iç iletişimiyle ilgili aşağıdaki önemli noktaları göz önünde bulundurun -
No performance gain - İş parçacıkları ve süreçler arasında doğru iletişimi sağlayamazsak, eşzamanlılık ve paralellikten elde edilen performans kazanımlarının faydası olmaz.
Accomplish task properly - İş parçacıkları arasında uygun bir iletişim mekanizması olmadan, atanan görev düzgün bir şekilde tamamlanamaz.
More efficient than inter-process communication - İş parçacıkları arası iletişim, süreçler arası iletişimden daha verimli ve kullanımı kolaydır, çünkü bir süreç içindeki tüm iş parçacıkları aynı adres alanını paylaşır ve paylaşılan belleği kullanmaları gerekmez.
İş parçacığı güvenli iletişim için Python veri yapıları
Çok iş parçacıklı kod, bir iş parçacığından başka bir iş parçacığına bilgi geçirme sorunu ile ortaya çıkar. Standart iletişim ilkeleri bu sorunu çözmez. Bu nedenle, iletişimi güvenli hale getirmek için nesneleri iş parçacıkları arasında paylaşmak için kendi bileşik nesnemizi uygulamamız gerekir. Aşağıda, bazı değişiklikler yaptıktan sonra iş parçacığı güvenli iletişim sağlayan birkaç veri yapısı bulunmaktadır:
Setleri
Set veri yapısını iş parçacığı açısından güvenli bir şekilde kullanmak için, kendi kilitleme mekanizmamızı uygulamak için set sınıfını genişletmemiz gerekir.
Misal
İşte sınıfı genişletmenin bir Python örneği -
class extend_class(set):
def __init__(self, *args, **kwargs):
self._lock = Lock()
super(extend_class, self).__init__(*args, **kwargs)
def add(self, elem):
self._lock.acquire()
try:
super(extend_class, self).add(elem)
finally:
self._lock.release()
def delete(self, elem):
self._lock.acquire()
try:
super(extend_class, self).delete(elem)
finally:
self._lock.release()
Yukarıdaki örnekte, adında bir sınıf nesnesi extend_class Python'dan miras alınan tanımlanmıştır set class. Bu sınıfın yapıcısı içinde bir kilit nesnesi oluşturulur. Şimdi, iki işlev var -add() ve delete(). Bu işlevler tanımlanmıştır ve iş parçacığı açısından güvenlidir. İkisi de güveniyorsuper bir anahtar istisna dışında sınıf işlevselliği.
Dekoratör
Bu, iş parçacığı güvenli iletişim için başka bir anahtar yöntem, dekoratörlerin kullanılmasıdır.
Misal
Dekoratörlerin nasıl kullanılacağını gösteren bir Python örneği düşünün & mminus;
def lock_decorator(method):
def new_deco_method(self, *args, **kwargs):
with self._lock:
return method(self, *args, **kwargs)
return new_deco_method
class Decorator_class(set):
def __init__(self, *args, **kwargs):
self._lock = Lock()
super(Decorator_class, self).__init__(*args, **kwargs)
@lock_decorator
def add(self, *args, **kwargs):
return super(Decorator_class, self).add(elem)
@lock_decorator
def delete(self, *args, **kwargs):
return super(Decorator_class, self).delete(elem)
Yukarıdaki örnekte, Python yöntem sınıfından ayrıca miras alınan lock_decorator adlı bir dekoratör yöntemi tanımlanmıştır. Daha sonra bu sınıfın yapıcısı içinde bir kilit nesnesi oluşturulur. Şimdi iki işlev var - ekle () ve sil (). Bu işlevler tanımlanmıştır ve iş parçacığı açısından güvenlidir. Her ikisi de bir önemli istisna dışında süper sınıf işlevselliğe güveniyor.
Listeler
Liste veri yapısı iş parçacığı açısından güvenlidir, hızlıdır ve geçici, bellek içi depolama için kolay bir yapıdır. Cpython'da, GIL bunlara eş zamanlı erişime karşı koruma sağlar. Listelerin iş parçacığı açısından güvenli olduğunu öğrendiğimizde, peki ya içlerinde yatan veriler. Aslında listenin verileri korunmuyor. Örneğin,L.append(x)başka bir iş parçacığı aynı şeyi yapmaya çalışıyorsa beklenen sonucu döndürme garantisi verilmez. Bunun nedeni,append() atomik bir işlemdir ve iş parçacığı güvenlidir, ancak diğer iş parçacığı listenin verilerini eşzamanlı olarak değiştirmeye çalışıyor, bu nedenle yarış koşullarının çıktı üzerindeki yan etkilerini görebiliyoruz.
Bu tür bir sorunu çözmek ve verileri güvenli bir şekilde değiştirmek için, birden fazla iş parçacığının potansiyel olarak yarış koşullarında çalışmamasını sağlayan uygun bir kilitleme mekanizması uygulamalıyız. Doğru kilitleme mekanizmasını uygulamak için, önceki örneklerde yaptığımız gibi sınıfı genişletebiliriz.
Listelerdeki diğer bazı atomik işlemler aşağıdaki gibidir -
L.append(x)
L1.extend(L2)
x = L[i]
x = L.pop()
L1[i:j] = L2
L.sort()
x = y
x.field = y
D[x] = y
D1.update(D2)
D.keys()
Burada -
- L, L1, L2 hepsi listedir
- D, D1, D2 diktedir
- x, y nesnelerdir
- i, j ints
Kuyruklar
Listenin verileri korunmuyorsa, sonuçlarla yüzleşmek zorunda kalabiliriz. Yarış koşullarının yanlış veri öğesini alabilir veya silebiliriz. Bu nedenle kuyruk veri yapısının kullanılması önerilir. Gerçek dünyadaki kuyruk örneği, aracın ilk girdiği ve ilk çıktığı tek şeritli tek yönlü bir yol olabilir. Bilet gişelerindeki ve otobüs duraklarındaki kuyruklardan daha fazla gerçek dünya örneği görülebilir.
Kuyruklar varsayılan olarak iş parçacığı güvenli veri yapılarıdır ve karmaşık kilitleme mekanizmasını uygulama konusunda endişelenmemize gerek yoktur. Python bize
Kuyruk Türleri
Bu bölümde, farklı kuyruk türleri hakkında kazanacağız. Python, siteden kullanmak için üç sıra seçeneği sağlar.<queue> modül -
- Normal Kuyruklar (FIFO, İlk Giren İlk Çıkar)
- LIFO, Son Giren İlk Çıkar
- Priority
Sonraki bölümlerde farklı kuyruklar hakkında bilgi edineceğiz.
Normal Kuyruklar (FIFO, İlk Giren İlk Çıkar)
Python tarafından sunulan en yaygın kullanılan kuyruk uygulamalarıdır. Bu kuyruk mekanizmasında kim önce gelirse önce hizmeti alır. FIFO, normal kuyruklar olarak da adlandırılır. FIFO kuyrukları aşağıdaki gibi gösterilebilir -
FIFO Kuyruğunun Python Uygulaması
Python'da, FIFO kuyruğu tek iş parçacığı ve çoklu iş parçacığı ile uygulanabilir.
Tek iş parçacıklı FIFO kuyruğu
FIFO kuyruğunu tek iş parçacığı ile uygulamak için, Queuesınıfı, temel bir ilk giren ilk çıkar kapsayıcı uygulayacaktır. Öğeler, kullanılarak dizinin bir "sonuna" eklenecektirput()ve kullanılarak diğer ucundan kaldırıldı get().
Misal
Aşağıda, tek iş parçacığı ile FIFO kuyruğunun uygulanması için bir Python programı verilmiştir -
import queue
q = queue.Queue()
for i in range(8):
q.put("item-" + str(i))
while not q.empty():
print (q.get(), end = " ")
Çıktı
item-0 item-1 item-2 item-3 item-4 item-5 item-6 item-7
Çıktı, yukarıdaki programın, öğelerin yerleştirildikleri sırayla kuyruktan kaldırıldığını göstermek için tek bir iş parçacığı kullandığını gösterir.
Birden çok iş parçacığı içeren FIFO kuyruğu
FIFO'yu birden çok iş parçacığı ile uygulamak için, kuyruk modülünden genişletilen myqueue () işlevini tanımlamamız gerekir. Get () ve put () yöntemlerinin çalışması, FIFO kuyruğunu tek iş parçacığı ile uygularken yukarıda tartışılanla aynıdır. Daha sonra onu çok iş parçacıklı hale getirmek için, evreleri bildirmemiz ve başlatmamız gerekir. Bu iş parçacıkları kuyruğu FIFO tarzında tüketecektir.
Misal
Aşağıda, birden çok iş parçacığı içeren FIFO kuyruğunun uygulanması için bir Python programı verilmiştir
import threading
import queue
import random
import time
def myqueue(queue):
while not queue.empty():
item = queue.get()
if item is None:
break
print("{} removed {} from the queue".format(threading.current_thread(), item))
queue.task_done()
time.sleep(2)
q = queue.Queue()
for i in range(5):
q.put(i)
threads = []
for i in range(4):
thread = threading.Thread(target=myqueue, args=(q,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
Çıktı
<Thread(Thread-3654, started 5044)> removed 0 from the queue
<Thread(Thread-3655, started 3144)> removed 1 from the queue
<Thread(Thread-3656, started 6996)> removed 2 from the queue
<Thread(Thread-3657, started 2672)> removed 3 from the queue
<Thread(Thread-3654, started 5044)> removed 4 from the queue
LIFO, İlk Giren Son Sırada
Bu kuyruk, FIFO (İlk Giren İlk Çıkar) kuyruklarından tamamen zıt bir analoji kullanır. Bu kuyruk mekanizmasında en son gelen, önce servisi alacaktır. Bu, yığın veri yapısını uygulamaya benzer. LIFO kuyrukları, yapay zeka algoritmaları gibi Derinlik öncelikli arama uygularken faydalı olduğunu kanıtladı.
LIFO kuyruğunun Python uygulaması
Python'da, LIFO kuyruğu tek iş parçacığı ve çoklu iş parçacığı ile uygulanabilir.
Tek iş parçacıklı LIFO kuyruğu
LIFO kuyruğunu tek iş parçacığı ile uygulamak için, Queue sınıf, yapıyı kullanarak temel bir son giren ilk çıkar kapsayıcı uygulayacak Queue.LifoQueue. Şimdi aradığımdaput(), elemanlar kabın kafasına eklenir ve kullanımda da kafadan çıkarılır. get().
Misal
Aşağıda, LIFO kuyruğunun tek iş parçacığı ile uygulanması için bir Python programı verilmiştir -
import queue
q = queue.LifoQueue()
for i in range(8):
q.put("item-" + str(i))
while not q.empty():
print (q.get(), end=" ")
Output:
item-7 item-6 item-5 item-4 item-3 item-2 item-1 item-0
Çıktı, yukarıdaki programın, elemanların eklendikleri sıranın tersi sırayla kuyruktan kaldırıldığını göstermek için tek bir iş parçacığı kullandığını gösterir.
Birden çok iş parçacığı içeren LIFO kuyruğu
Uygulama, FIFO kuyruklarının çoklu iş parçacığı ile uygulanmasını yaptığımız gibi benzerdir. Tek fark, kullanmamız gerektiğidir.Queue yapıyı kullanarak temel bir son giren ilk çıkar kapsayıcı uygulayacak sınıf Queue.LifoQueue.
Misal
Aşağıda, LIFO kuyruğunun birden çok iş parçacığı ile uygulanması için bir Python programı verilmiştir -
import threading
import queue
import random
import time
def myqueue(queue):
while not queue.empty():
item = queue.get()
if item is None:
break
print("{} removed {} from the queue".format(threading.current_thread(), item))
queue.task_done()
time.sleep(2)
q = queue.LifoQueue()
for i in range(5):
q.put(i)
threads = []
for i in range(4):
thread = threading.Thread(target=myqueue, args=(q,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
Çıktı
<Thread(Thread-3882, started 4928)> removed 4 from the queue
<Thread(Thread-3883, started 4364)> removed 3 from the queue
<Thread(Thread-3884, started 6908)> removed 2 from the queue
<Thread(Thread-3885, started 3584)> removed 1 from the queue
<Thread(Thread-3882, started 4928)> removed 0 from the queue
Öncelik sırası
FIFO ve LIFO kuyruklarında, öğelerin sırası yerleştirme sırasına bağlıdır. Ancak, önceliğin yerleştirme sırasından daha önemli olduğu birçok durum vardır. Gerçek dünya örneğini ele alalım. Havaalanındaki güvenliğin farklı kategorilerdeki insanları kontrol ettiğini varsayalım. VVIP çalışanları, havayolu personeli, gümrük memuru, kategoriler sıradan kişiler gibi varış bazında kontrol edilmek yerine öncelikli olarak kontrol edilebilir.
Öncelik kuyruğu için dikkate alınması gereken bir diğer önemli husus, bir görev planlayıcısının nasıl geliştirileceğidir. Yaygın bir tasarım, kuyruktaki en ajan görevini öncelik temelinde sunmaktır. Bu veri yapısı, öncelik değerlerine göre öğeleri kuyruktan almak için kullanılabilir.
Öncelik Kuyruğunun Python Uygulaması
Python'da, öncelik kuyruğu tek iş parçacığı ile çoklu iş parçacığı ile uygulanabilir.
Tek iş parçacığı ile öncelik sırası
Tek iş parçacığı ile öncelik kuyruğu uygulamak için, Queue sınıf, yapıyı kullanarak öncelikli kapsayıcıda bir görev uygulayacak Queue.PriorityQueue. Şimdi aradığımdaput()öğeler, en düşük değerin en yüksek önceliğe sahip olacağı bir değerle eklenir ve bu nedenle ilk önce kullanılarak alınır get().
Misal
Tek iş parçacığı ile Öncelik kuyruğunun uygulanması için aşağıdaki Python programını düşünün -
import queue as Q
p_queue = Q.PriorityQueue()
p_queue.put((2, 'Urgent'))
p_queue.put((1, 'Most Urgent'))
p_queue.put((10, 'Nothing important'))
prio_queue.put((5, 'Important'))
while not p_queue.empty():
item = p_queue.get()
print('%s - %s' % item)
Çıktı
1 – Most Urgent
2 - Urgent
5 - Important
10 – Nothing important
Yukarıdaki çıktıda, sıranın öğeleri önceliğe göre sakladığını görebiliriz - daha az değer yüksek önceliğe sahip.
Çok iş parçacıklı öncelik sırası
Uygulama, birden çok iş parçacığı içeren FIFO ve LIFO kuyruklarının uygulanmasına benzer. Tek fark, kullanmamız gerektiğidir.Queue yapıyı kullanarak önceliği başlatmak için sınıf Queue.PriorityQueue. Diğer bir fark, kuyruğun oluşturulma şeklidir. Aşağıda verilen örnekte, iki özdeş veri seti ile üretilecektir.
Misal
Aşağıdaki Python programı, birden çok iş parçacığı ile öncelik sırasının uygulanmasına yardımcı olur -
import threading
import queue
import random
import time
def myqueue(queue):
while not queue.empty():
item = queue.get()
if item is None:
break
print("{} removed {} from the queue".format(threading.current_thread(), item))
queue.task_done()
time.sleep(1)
q = queue.PriorityQueue()
for i in range(5):
q.put(i,1)
for i in range(5):
q.put(i,1)
threads = []
for i in range(2):
thread = threading.Thread(target=myqueue, args=(q,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
Çıktı
<Thread(Thread-4939, started 2420)> removed 0 from the queue
<Thread(Thread-4940, started 3284)> removed 0 from the queue
<Thread(Thread-4939, started 2420)> removed 1 from the queue
<Thread(Thread-4940, started 3284)> removed 1 from the queue
<Thread(Thread-4939, started 2420)> removed 2 from the queue
<Thread(Thread-4940, started 3284)> removed 2 from the queue
<Thread(Thread-4939, started 2420)> removed 3 from the queue
<Thread(Thread-4940, started 3284)> removed 3 from the queue
<Thread(Thread-4939, started 2420)> removed 4 from the queue
<Thread(Thread-4940, started 3284)> removed 4 from the queue