Sık sık listeler yerine birden çok iş parçacığı ile kuyruk kullanılması önerilmektedir .pop()
. Bunun nedeni listelerin iş parçacığı açısından güvenli olmaması veya başka bir nedenle mi?
Sık sık listeler yerine birden çok iş parçacığı ile kuyruk kullanılması önerilmektedir .pop()
. Bunun nedeni listelerin iş parçacığı açısından güvenli olmaması veya başka bir nedenle mi?
Yanıtlar:
Listelerin kendisi iş parçacığı için güvenlidir. CPython'da GIL, onlara eşzamanlı erişimlere karşı koruma sağlar ve diğer uygulamalar, liste uygulamaları için ince taneli bir kilit veya senkronize bir veri türü kullanmaya özen gösterir. Ancak, listelerin kendileri eşzamanlı olarak erişim girişimleriyle bozulamazken, listenin verileri korunmaz. Örneğin:
L[0] += 1
+=
atomik bir işlem olmadığı için başka bir iplik aynı şeyi yaparsa, L [0] değerini bir arttırması garanti edilmez. (Python'da çok, çok az işlem aslında atomiktir, çünkü bunların çoğu rastgele Python kodunun çağrılmasına neden olabilir.) Kuyrukları kullanmalısınız çünkü sadece korumasız bir liste kullanırsanız , yarış nedeniyle yanlış öğeyi alabilir veya silebilirsiniz koşullar.
Thomas'ın mükemmel cevabındaki bir noktayı açıklığa kavuşturmak için append()
, ipliğin güvenli olduğu belirtilmelidir .
Bunun nedeni , yazmaya gittiğimizde okunan verilerin aynı yerde olacağına dair bir endişe olmamasıdır. İşlem veri okumaz, sadece listeye veri yazar.append()
PyList_Append
bir GIL kilidinde yapılır. Eklenecek nesneye bir başvuru verilir. Söz konusu nesnenin içeriği değerlendirildikten ve çağrı PyList_Append
yapılmadan önce değiştirilebilir . Ama yine de aynı nesne ve güvenli bir şekilde (bunu yaparsanız eklenmiş olacak lst.append(x); ok = lst[-1] is x
, o zaman ok
tabii ki, yanlış olabilir). Başvurduğunuz kod, INCREF dışında, eklenen nesneden okumaz. Eklenen listeyi okur ve yeniden tahsis edebilir.
L[0] += x
bir __getitem__
açık L
ve sonra bir __setitem__
açık gerçekleştirecek L
- eğer L
destekliyorsa __iadd__
, nesne arayüzünde işleri biraz farklı yapacak, ancak L
python yorumlayıcı düzeyinde hala iki ayrı işlem var (bunları derlenmiş bayt kodu). append
Byte aa tek yöntem çağrısı yapılır.
remove
?
Aşağıda , list
işlem örneklerinin kapsamlı ve tam kapsamlı olmayan bir listesi ve bunların iş parçacığı açısından güvenli olup olmadıkları verilmiştir. Buradaobj in a_list
dil yapısı ile ilgili bir cevap almayı umuyorum .
Geçenlerde bir iş parçacığında sürekli bir listeye eklemek, öğeler arasında döngü ve öğenin hazır olup olmadığını kontrol etmek için gerekli bir durum vardı, benim durumumda bir AsyncResult oldu ve sadece hazır olduğunda listeden kaldırmak. Sorunumu açıkça gösteren herhangi bir örnek bulamadım Burada sürekli bir iş parçacığına listeye ekleme ve sürekli başka bir iş parçacığında aynı listeden kaldırma gösteren bir örnek Kusurlu sürümü daha küçük sayılar üzerinde kolayca çalışır, ancak sayıları yeterince büyük tutmak ve çalıştırmak birkaç kez ve hatayı göreceksiniz
FLAWED sürümü
import threading
import time
# Change this number as you please, bigger numbers will get the error quickly
count = 1000
l = []
def add():
for i in range(count):
l.append(i)
time.sleep(0.0001)
def remove():
for i in range(count):
l.remove(i)
time.sleep(0.0001)
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()
print(l)
ERROR olduğunda çıktı
Exception in thread Thread-63:
Traceback (most recent call last):
File "/Users/zup/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run()
File "/Users/zup/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "<ipython-input-30-ecfbac1c776f>", line 13, in remove
l.remove(i)
ValueError: list.remove(x): x not in list
Kilitleri kullanan sürüm
import threading
import time
count = 1000
l = []
lock = threading.RLock()
def add():
with lock:
for i in range(count):
l.append(i)
time.sleep(0.0001)
def remove():
with lock:
for i in range(count):
l.remove(i)
time.sleep(0.0001)
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()
print(l)
Çıktı
[] # Empty list
Sonuç
Önceki yanıtlarda belirtildiği gibi, listenin kendisine öğe ekleme veya patlama eylemi iş parçacığı için güvenliyken, iş parçacığı için güvenli olmayan bir iş parçacığı eklediğinizde ve başka bir iş parçacığında pop yaptığınız zaman
with r:
açıkça r.acquire()
r.release()