क्या सूचियाँ धागा-सुरक्षित हैं?


155

मैं ध्यान देता हूं कि अक्सर सूचियों के बजाय और कई थ्रेड्स के साथ कतारों का उपयोग करने का सुझाव दिया जाता है .pop()। क्या यह इसलिए है क्योंकि सूचियाँ थ्रेड-सुरक्षित नहीं हैं, या किसी अन्य कारण से?


1
हमेशा यह बताना मुश्किल है कि पायथन में थ्रेड-सेफ की क्या गारंटी है, और इसमें थ्रेड सुरक्षा के बारे में तर्क करना मुश्किल है। यहां तक ​​कि अत्यधिक लोकप्रिय बिटकॉइन वॉलेट इलेक्ट्रम में कंसीलर बग्स होने की संभावना है जो इससे उपजी है।
सूडो

जवाबों:


182

सूचियाँ स्वयं थ्रेड-सुरक्षित हैं। CPython में GIL उनके लिए समवर्ती अभिगम से बचाता है, और अन्य कार्यान्वयन उनकी सूची कार्यान्वयन के लिए एक महीन दाने वाले लॉक या एक सिंक्रनाइज़ डेटाटाइप का उपयोग करने के लिए ध्यान रखते हैं। हालाँकि, जबकि सूचियाँ स्वयं समवर्ती पहुँच के प्रयासों से भ्रष्ट नहीं हो सकती हैं, सूचियों का डेटा संरक्षित नहीं है। उदाहरण के लिए:

L[0] += 1

वास्तव में एल [0] को बढ़ाने की गारंटी नहीं है यदि एक और धागा एक ही काम करता है, क्योंकि +=एक परमाणु ऑपरेशन नहीं है। (बहुत, पायथन में बहुत कम संचालन वास्तव में परमाणु हैं, क्योंकि उनमें से अधिकांश को मनमाने ढंग से पायथन कोड कहा जा सकता है।) आपको क्युस का उपयोग करना चाहिए क्योंकि यदि आप सिर्फ एक असुरक्षित सूची का उपयोग करते हैं, तो आप दौड़ के कारण गलत तरीके से प्राप्त कर सकते हैं या हटा सकते हैं। शर्तेँ।


1
क्या धागा भी सुरक्षित है? यह मेरे उपयोग के लिए अधिक उपयुक्त लगता है।
लीमंट

20
सभी पायथन ऑब्जेक्ट्स में एक ही तरह की थ्रेड-सेफनेस है - वे स्वयं भ्रष्ट नहीं हैं, लेकिन उनका डेटा हो सकता है। collection.deque क्यू के पीछे क्या है। क्यू वस्तुओं। यदि आप दो थ्रेड से चीजों को एक्सेस कर रहे हैं, तो आपको वास्तव में Queue.Queue ऑब्जेक्ट का उपयोग करना चाहिए। वास्तव में।
थॉमस वॉटर्स

10
लेमिएंट, डेक्स थ्रेड-सेफ है। धाराप्रवाह पायथन के अध्याय 2 से: "क्लास कलेक्शन.डेक एक थ्रेड-सेफ डबल-एंडेड कतार है, जिसे दोनों सिरों से तेजी से डालने और हटाने के लिए डिज़ाइन किया गया है। [...] एपेंड और पॉपलेफ्ट ऑपरेशंस परमाणु हैं, इसलिए डीक सुरक्षित है। ताले का उपयोग करने की आवश्यकता के बिना बहु-थ्रेडेड अनुप्रयोगों में एक LIFO- कतार के रूप में उपयोग करें। "
अल स्वेगार्ट

3
क्या यह उत्तर CPython के बारे में है या Python के बारे में है? खुद पायथन के लिए क्या जवाब है?
user541686

@ निल्स: उह, पहला पेज जिसे आप ने अजगर के बजाय पायथन से जोड़ा है क्योंकि यह पायथन भाषा का वर्णन कर रहा है। और वह दूसरी कड़ी शाब्दिक रूप से कहती है कि पायथन भाषा के कई कार्यान्वयन हैं, बस एक ऐसा होता है जो अधिक लोकप्रिय होता है। यह देखते हुए कि प्रश्न पायथन के बारे में था, उत्तर में वर्णन किया जाना चाहिए कि अजगर के किसी भी अनुरूप कार्यान्वयन में क्या होने की गारंटी दी जा सकती है, न कि विशेष रूप से सीपीथॉन में क्या होता है।
user541686

89

थॉमस के उत्कृष्ट जवाब में एक बिंदु को स्पष्ट करने के लिए, यह उल्लेखनीय है कि append() है धागा सुरक्षित।

ऐसा इसलिए है क्योंकि इस बात की कोई चिंता नहीं है कि पढ़ा जा रहा डेटा एक ही जगह पर होगा जब हम इसे लिखने के लिए जाएंगे । append()आपरेशन डेटा को पढ़ने नहीं है, यह केवल सूची में डेटा लिखता है।


1
PyList_Append मेमोरी से पढ़ रहा है। क्या आपका मतलब यह है कि इसका रीड एंड राइट उसी GIL लॉक में होता है? github.com/python/cpython/blob/…
amwinter

1
@amwinter हां, पूरी कॉल PyList_Appendएक GIL लॉक में की जाती है। इसे संलग्न करने के लिए किसी वस्तु का संदर्भ दिया जाता है। मूल्यांकन के बाद और कॉल किए जाने PyList_Appendसे पहले उस ऑब्जेक्ट की सामग्री को बदला जा सकता है। लेकिन यह अभी भी एक ही वस्तु होगी, और सुरक्षित रूप से संलग्न (यदि आप करते हैं lst.append(x); ok = lst[-1] is x, तो okगलत हो सकता है, निश्चित रूप से)। आपके द्वारा संदर्भित कोड एपेंडेड ऑब्जेक्ट से नहीं पढ़ा जाता है, सिवाय इसके INCREF के। यह पढ़ता है, और पुनः प्राप्त कर सकता है, जो सूची में जोड़ा गया है।
ग्राग्गो

3
dotancohen की बात यह है कि है L[0] += xएक प्रदर्शन करेंगे __getitem__पर Lऔर फिर एक __setitem__पर Lहैं - Lका समर्थन करता है __iadd__यह वस्तु इंटरफेस पर अलग ढंग से एक सा काम करेंगे, लेकिन अभी भी पर दो अलग-अलग संचालन कर रहे हैं Lअजगर दुभाषिया के स्तर पर (आप उन्हें में देखेंगे संकलित bytecode)। appendबाईटकोड में आ ही विधि कॉल में किया जाता है।
ग्रोगो

6
कैसे के बारे में remove?
acrazing

2
upvoted! तो क्या मैं एक धागे में लगातार और दूसरे धागे में पॉप कर सकता हूं?
PirateApp

38

यहाँ उदाहरण के लिए एक व्यापक अभी तक संक्षिप्त सूची है की listसंचालन और चाहे या नहीं वे धागा सुरक्षित हैं। यहांobj in a_list भाषा निर्माण के संबंध में उत्तर पाने की आशा है


पहला लिंक मृत प्रतीत होता है।
डिस्प्लेनेम

2

मेरे पास हाल ही में यह मामला था, जहाँ मुझे एक सूची में लगातार एक थ्रेड में संलग्न करने की आवश्यकता थी, आइटम के माध्यम से लूप और जाँच करें कि क्या आइटम तैयार था, यह मेरे मामले में एक AsyncResult था और इसे सूची से केवल तभी हटा दें जब यह तैयार था। मुझे कोई भी ऐसा उदाहरण नहीं मिला जिससे मेरी समस्या स्पष्ट रूप से प्रदर्शित हो। एक उदाहरण है कि एक थ्रेड में लगातार सूची में जोड़ने और दूसरे थ्रेड में एक ही सूची से लगातार निकालने का प्रदर्शन किया जाता है। त्रुटिपूर्ण संस्करण छोटी संख्याओं पर आसानी से चलता है, लेकिन संख्याओं को बड़ा रखें और एक रन बनाएं कुछ समय और आप त्रुटि देखेंगे

फ्लावेड संस्करण

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

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

संस्करण जो ताले का उपयोग करता है

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)

उत्पादन

[] # Empty list

निष्कर्ष

जैसा कि पहले के उत्तरों में उल्लेख किया गया है जबकि सूची से तत्वों को जोड़ने या पॉप करने का कार्य स्वयं थ्रेड सुरक्षित है, जो थ्रेड सुरक्षित नहीं है, जब आप एक थ्रेड में संलग्न होते हैं और दूसरे में पॉप करते हैं


6
ताले वाले संस्करण में ताले के बिना एक जैसा व्यवहार होता है। मूल रूप से त्रुटि आ रही है क्योंकि यह कुछ हटाने की कोशिश कर रहा है जो सूची में नहीं है, इसका थ्रेड सुरक्षा से कोई लेना-देना नहीं है। प्रारंभ क्रम बदलने के बाद तालों के साथ संस्करण को चलाने का प्रयास करें अर्थात t1 को t1 से पहले शुरू करें और आपको वही त्रुटि दिखाई देगी। जब भी t2 t1 से आगे निकलता है तो आप ताले का उपयोग करते हैं या नहीं, इसमें कोई त्रुटि नहीं होगी।
देव

1
इसके अलावा, आप with r:स्पष्ट रूप से कॉल करने के बजाय एक संदर्भ प्रबंधक ( ) का उपयोग करने से बेहतर हैं r.acquire()औरr.release()
गॉर्डन ऐज

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.