थ्रेडसेफ़ बनाम फिर से प्रवेश


89

हाल ही में, मैंने एक सवाल पूछा, शीर्षक के साथ "क्या मॉलोक धागा सुरक्षित है?" , और उसके अंदर मैंने पूछा, "क्या मॉलॉक फिर से प्रवेश कर रहा है?"

मैं इस धारणा के तहत था कि सभी पुनः प्रवेश करने वाले थ्रेड-सुरक्षित हैं।

क्या यह धारणा गलत है?

जवाबों:


42

री-एंट्रेंट फ़ंक्शंस वैश्विक चर पर निर्भर नहीं होते हैं जो सी लाइब्रेरी हेडर में उजागर होते हैं .. सी में उदाहरण के लिए strtok () बनाम strtok_r () लेते हैं।

कुछ कार्यों को 'कार्य प्रगति पर' संग्रहीत करने के लिए जगह की आवश्यकता होती है, फिर से प्रवेश करने वाले कार्य आपको इस सूचक को थ्रेड के स्वयं के भंडारण के भीतर निर्दिष्ट करने की अनुमति देते हैं, वैश्विक रूप से नहीं। चूंकि यह संग्रहण कॉलिंग फ़ंक्शन के लिए अनन्य है, इसलिए इसे बाधित किया जा सकता है और फिर से प्रवेश किया जा सकता है (पुनः प्रवेशी) और चूंकि अधिकांश मामलों में कार्य बहिष्कार के लिए पारस्परिक क्रियाओं से परे काम करने के लिए आवश्यक नहीं है, उन्हें अक्सर माना जाता है धागा सुरक्षित । हालाँकि, यह परिभाषा द्वारा गारंटीकृत नहीं है।

ग़लती से, हालांकि, POSIX सिस्टम पर थोड़ा अलग मामला है (और यह कैसे काम करता है, इसके बारे में किसी भी स्पष्टीकरण में ऑडबॉल बताता है) :)

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

लेकिन, सभी कार्यों के लिए या तो जांच करने की आवश्यकता नहीं है। malloc()कोई आवश्यकता नहीं है, यह किसी भी दिए गए धागे के लिए प्रवेश बिंदु के दायरे से बाहर कुछ भी निर्भर नहीं करता है (और खुद धागा सुरक्षित है)।

कार्यात्मक रूप से आवंटित मान लौटाने वाले कार्य म्यूटेक्स, फ़ुटेक्स या अन्य परमाणु लॉकिंग तंत्र के उपयोग के बिना थ्रेड सुरक्षित नहीं हैं । यदि वे बाधित नहीं होने जा रहे हैं, तब भी, उन्हें फिर से तैयार होने की आवश्यकता नहीं है।

अर्थात:

static char *foo(unsigned int flags)
{
  static char ret[2] = { 0 };

  if (flags & FOO_BAR)
    ret[0] = 'c';
  else if (flags & BAR_FOO)
    ret[0] = 'd';
  else
    ret[0] = 'e';

  ret[1] = 'A';

  return ret;
}

इसलिए, जैसा कि आप देख सकते हैं, कई थ्रेड्स का उपयोग करना जो बिना किसी प्रकार के लॉकिंग के एक आपदा होगी .. लेकिन इसका कोई उद्देश्य नहीं है। जब डायनामिक रूप से आबंटित मेमोरी कुछ एम्बेडेड प्लेटफ़ॉर्म पर वर्जित होती है तो आप उसमें भाग लेंगे।

विशुद्ध रूप से कार्यात्मक प्रोग्रामिंग में, रेफरेंट अक्सर थ्रेड को सुरक्षित नहीं करता है , यह परिभाषित या अनाम फ़ंक्शन के कार्य पर निर्भर करेगा जो फ़ंक्शन प्रविष्टि बिंदु, पुनरावृत्ति, आदि के लिए पारित किया गया है।

Safe थ्रेड सेफ ’डालने का एक बेहतर तरीका समवर्ती पहुंच के लिए सुरक्षित है , जो बेहतर रूप से आवश्यकता को दर्शाता है।


2
रेंटेंट थ्रेड-सुरक्षित नहीं है। शुद्ध कार्य थ्रेड-सुरक्षा का अर्थ है।
जूलियो गुएरा

शानदार जवाब टिम। बस स्पष्ट करने के लिए, आपकी "अक्सर" से मेरी समझ यह है कि थ्रेड-सेफ का मतलब यह नहीं है कि रीफ्रेंट, लेकिन रीक्रेंट भी थ्रेड-सेफ़ नहीं है। यदि आप एक रैत्रांत समारोह जो है, इसके उदाहरण खोजने के लिए सक्षम हो जाएगा नहीं थ्रेड-सुरक्षित?
रिकार्डो

@ टिम पोस्ट "संक्षेप में, रेफरेंट का अर्थ अक्सर थ्रेड सेफ़ होता है (जैसे कि" उस फ़ंक्शन के रीएन्ट्रेंट संस्करण का उपयोग करें यदि आप थ्रेड्स का उपयोग कर रहे हैं "), लेकिन थ्रेड सेफ़ का अर्थ हमेशा री-एंट्रेंट नहीं होता है।" qt इसके विपरीत कहता है : "इसलिए, एक थ्रेड-सेफ फंक्शन हमेशा रीवेंटेंट होता है, लेकिन एक रेंटेंट फ़ंक्शन हमेशा थ्रेड-सेफ नहीं होता है।"
डब्लू 0

और विकिपीडिया अभी तक कुछ और कहता है: "बहु-थ्रेडेड परिवेशों में पुन: सुरक्षा की यह परिभाषा थ्रेड-सेफ्टी से भिन्न होती है। एक रीएंन्ट्रेंट सब्रूटिन थ्रेड-सेफ्टी प्राप्त कर सकता है, [1] लेकिन केवल रीक्रेंट होने से थ्रेड-सेफ होने के लिए पर्याप्त नहीं हो सकता है। सभी स्थितियों में। इसके विपरीत, थ्रेड-सेफ कोड जरूरी नहीं है कि वह
रीइंटरेंट

@ रीकोर्डो: अस्थिर चर के माध्यम से सिंक्रनाइज़ किए गए कार्य लेकिन सिग्नल / इंटरप्ट हैंडलर के साथ उपयोग के लिए पूर्ण मेमोरी बाधाएं आमतौर पर फिर से प्रवेश नहीं बल्कि थ्रेड-सुरक्षित हैं।
14

77

टीएल; डीआर: एक फ़ंक्शन, दोनों या दोनों में, फिर से, थ्रेड-सुरक्षित हो सकता है।

थ्रेड-सेफ्टी और रीटर्रेंसी के लिए विकिपीडिया के लेख पढ़ने लायक हैं। यहाँ कुछ उद्धरण हैं:

एक फ़ंक्शन थ्रेड-सुरक्षित है यदि:

यह केवल उसी तरीके से साझा डेटा संरचनाओं में हेरफेर करता है जो एक ही समय में कई थ्रेड्स द्वारा सुरक्षित निष्पादन की गारंटी देता है।

एक समारोह है रैत्रांत यदि:

इसके निष्पादन के दौरान इसे किसी भी बिंदु पर बाधित किया जा सकता है और फिर इसके पिछले चालान पूर्ण निष्पादन से पहले सुरक्षित रूप से फिर से ("पुनः दर्ज") कहा जा सकता है।

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

भ्रम से बचने की कुंजी यह है कि रीएन्ट्रेंट केवल एक थ्रेड निष्पादन को संदर्भित करता है। यह उस समय से एक अवधारणा है जब कोई मल्टीटास्किंग ऑपरेटिंग सिस्टम मौजूद नहीं था।

उदाहरण

(विकिपीडिया लेखों से थोड़ा संशोधित)

उदाहरण 1: थ्रेड-सेफ़ नहीं, रीटरेंट नहीं

/* As this function uses a non-const global variable without
   any precaution, it is neither reentrant nor thread-safe. */

int t;

void swap(int *x, int *y)
{
    t = *x;
    *x = *y;
    *y = t;
}

उदाहरण 2: थ्रेड-सेफ़, रीटरेंट नहीं

/* We use a thread local variable: the function is now
   thread-safe but still not reentrant (within the
   same thread). */

__thread int t;

void swap(int *x, int *y)
{
    t = *x;
    *x = *y;
    *y = t;
}

उदाहरण 3: थ्रेड-सुरक्षित नहीं, रीएंटेंट

/* We save the global state in a local variable and we restore
   it at the end of the function.  The function is now reentrant
   but it is not thread safe. */

int t;

void swap(int *x, int *y)
{
    int s;
    s = t;
    t = *x;
    *x = *y;
    *y = t;
    t = s;
}

उदाहरण 4: थ्रेड-सेफ, रीएंटेंट

/* We use a local variable: the function is now
   thread-safe and reentrant, we have ascended to
   higher plane of existence.  */

void swap(int *x, int *y)
{
    int t;
    t = *x;
    *x = *y;
    *y = t;
}

10
मुझे पता है कि मैं केवल धन्यवाद कहने के लिए टिप्पणी नहीं करने वाला हूं, लेकिन यह सबसे अच्छा चित्रण में से एक है जो फिर से प्रवेश और थ्रेड सुरक्षित कार्यों के बीच के अंतर को दर्शाता है। विशेष रूप से आपने बहुत स्पष्ट शब्दों का उपयोग किया है, और 4 श्रेणियों के बीच अंतर करने के लिए एक महान उदाहरण फ़ंक्शन को चुना है। तो धन्यवाद!
रयकर

11
यह मुझे लगता है कि थाय एक्सप्ले 3 रीवेंटेंट नहीं है: अगर एक सिग्नल हैंडलर, इंटरप्ट करने के बाद t = *x, कॉल करता है swap(), तो tओवरराइड हो जाएगा, जिससे अप्रत्याशित परिणाम हो सकते हैं।
रोम

1
@ SandBag_1996, आइए एक कॉल को swap(5, 6)बाधित होने पर विचार करें swap(1, 2)। के बाद t=*x, s=t_originalऔर t=5। अब, रुकावट के बाद, s=5और t=1। हालांकि, दूसरे swapरिटर्न से पहले यह संदर्भ को बहाल कर देगा, जिससे t=s=5। अब, हम पहले के swapसाथ वापस जाते हैं t=5 and s=t_originalऔर उसके बाद जारी रखते हैं t=*x। तो, फ़ंक्शन फिर से प्रवेश करता दिखाई देता है। याद रखें कि हर कॉल sको स्टैक पर आवंटित की अपनी कॉपी मिलती है ।
2

4
@ SandBag_1996 यह धारणा है कि यदि फ़ंक्शन बाधित हो जाता है (किसी भी बिंदु पर), इसे केवल फिर से कॉल किया जाना है, और हम मूल कॉल जारी रखने से पहले पूरा होने तक प्रतीक्षा करते हैं। कुछ और यदि ऐसा होता है, तो यह मूल रूप से multithreading है, और इस समारोह है नहीं धागा सुरक्षित। मान लीजिए कि फ़ंक्शन ABCD करता है, तो हम केवल AB_ABCD_CD, या A_ABCD_BCD, या A__AB_ABCD_CD__BCD जैसी चीज़ों को ही स्वीकार करते हैं। जैसा कि आप देख सकते हैं, उदाहरण 3 इन मान्यताओं के तहत ठीक काम करेगा, इसलिए यह रीएन्ट्रेंट है। उम्मीद है की यह मदद करेगा।
मिनीक्वायार्क

1
@ SandBag_1996, mutex वास्तव में इसे गैर-रीजेंट बना देगा। पहले आह्वान ताले म्यूटेक्स। दूसरे आह्वान में आता है - गतिरोध।
पूर्णनव

56

यह परिभाषा पर निर्भर करता है। उदाहरण के लिए Qt निम्नलिखित का उपयोग करता है:

  • एक थ्रेड-सुरक्षित * फ़ंक्शन को कई थ्रेड्स से एक साथ बुलाया जा सकता है, तब भी जब इनवोकेशन साझा डेटा का उपयोग करते हैं, क्योंकि साझा डेटा के सभी संदर्भ क्रमबद्ध होते हैं।

  • एक रिपेंटेंट फ़ंक्शन को एक साथ कई थ्रेड्स से भी बुलाया जा सकता है, लेकिन केवल अगर प्रत्येक इनवोकेशन अपने डेटा का उपयोग करता है।

इसलिए, एक थ्रेड-सुरक्षित फ़ंक्शन हमेशा रीएन्स्ट्रेंट होता है, लेकिन एक रेंटेंट फ़ंक्शन हमेशा थ्रेड-सुरक्षित नहीं होता है।

विस्तार से, एक वर्ग को पुनर्वित्त कहा जाता है यदि इसके सदस्य कार्यों को कई थ्रेड्स से सुरक्षित रूप से कहा जा सकता है, जब तक कि प्रत्येक थ्रेड क्लास के एक अलग उदाहरण का उपयोग करता है। कक्षा थ्रेड-सुरक्षित है यदि इसके सदस्य फ़ंक्शन को कई थ्रेड्स से सुरक्षित रूप से कॉल किया जा सकता है, भले ही सभी थ्रेड्स क्लास के एक ही उदाहरण का उपयोग करें।

लेकिन वे भी सावधानी:

नोट: मल्टीथ्रेडिंग डोमेन में शब्दावली पूरी तरह से मानकीकृत नहीं है। POSIX, रीवेंट्रेंट और थ्रेड-सेफ़ की परिभाषाओं का उपयोग करता है जो इसके C API के लिए कुछ अलग हैं। Qt के साथ अन्य ऑब्जेक्ट-ओरिएंटेड C ++ क्लास लाइब्रेरीज़ का उपयोग करते समय, सुनिश्चित करें कि परिभाषाएँ समझ में आती हैं।


2
रेंटेंट की यह परिभाषा बहुत मजबूत है।
क्वेरुईओप

यदि यह किसी भी वैश्विक / स्थिर संस्करण का उपयोग नहीं करता है, तो एक फ़ंक्शन दोनों रीक्रेंट और थ्रेड-सुरक्षित है। थ्रेड - सेफ: जब एक ही समय में कई थ्रेड्स आपके फ़ंक्शन को चलाते हैं, तो क्या कोई रेस है ?? यदि आप वैश्विक संस्करण का उपयोग करते हैं, तो इसे बचाने के लिए लॉक का उपयोग करें। इसलिए यह धागा सुरक्षित है। रीवेंट्रेंट: यदि आपके फ़ंक्शन के निष्पादन के दौरान कोई सिग्नल होता है, और अपने फ़ंक्शन को फिर से सिग्नल में कॉल करता है, तो क्या यह सुरक्षित है ??? इस तरह के मामले में, कई थ्रेड्स नहीं हैं। यह सबसे अच्छा है कि आपका कोई स्टैटिक / ग्लोबल
वर्जन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.