हाल ही में, मैंने एक सवाल पूछा, शीर्षक के साथ "क्या मॉलोक धागा सुरक्षित है?" , और उसके अंदर मैंने पूछा, "क्या मॉलॉक फिर से प्रवेश कर रहा है?"
मैं इस धारणा के तहत था कि सभी पुनः प्रवेश करने वाले थ्रेड-सुरक्षित हैं।
क्या यह धारणा गलत है?
हाल ही में, मैंने एक सवाल पूछा, शीर्षक के साथ "क्या मॉलोक धागा सुरक्षित है?" , और उसके अंदर मैंने पूछा, "क्या मॉलॉक फिर से प्रवेश कर रहा है?"
मैं इस धारणा के तहत था कि सभी पुनः प्रवेश करने वाले थ्रेड-सुरक्षित हैं।
क्या यह धारणा गलत है?
जवाबों:
री-एंट्रेंट फ़ंक्शंस वैश्विक चर पर निर्भर नहीं होते हैं जो सी लाइब्रेरी हेडर में उजागर होते हैं .. सी में उदाहरण के लिए 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 थ्रेड सेफ ’डालने का एक बेहतर तरीका समवर्ती पहुंच के लिए सुरक्षित है , जो बेहतर रूप से आवश्यकता को दर्शाता है।
टीएल; डीआर: एक फ़ंक्शन, दोनों या दोनों में, फिर से, थ्रेड-सुरक्षित हो सकता है।
थ्रेड-सेफ्टी और रीटर्रेंसी के लिए विकिपीडिया के लेख पढ़ने लायक हैं। यहाँ कुछ उद्धरण हैं:
एक फ़ंक्शन थ्रेड-सुरक्षित है यदि:
यह केवल उसी तरीके से साझा डेटा संरचनाओं में हेरफेर करता है जो एक ही समय में कई थ्रेड्स द्वारा सुरक्षित निष्पादन की गारंटी देता है।
एक समारोह है रैत्रांत यदि:
इसके निष्पादन के दौरान इसे किसी भी बिंदु पर बाधित किया जा सकता है और फिर इसके पिछले चालान पूर्ण निष्पादन से पहले सुरक्षित रूप से फिर से ("पुनः दर्ज") कहा जा सकता है।
संभव पुनरावृत्ति के उदाहरण के रूप में, विकिपीडिया सिस्टम द्वारा बाधित किए जाने के लिए डिज़ाइन किए गए एक फ़ंक्शन का उदाहरण देता है: मान लीजिए कि जब कोई अन्य व्यवधान होता है तो यह पहले से ही चल रहा होता है। लेकिन यह मत सोचिए कि आप सिर्फ इसलिए सुरक्षित हैं क्योंकि आप सिस्टम में बाधा नहीं डालते हैं: यदि आप कॉलबैक या पुनरावर्ती कार्यों का उपयोग करते हैं, तो आपको एकल-थ्रेडेड प्रोग्राम में रीटरेंस की समस्या हो सकती है।
भ्रम से बचने की कुंजी यह है कि रीएन्ट्रेंट केवल एक थ्रेड निष्पादन को संदर्भित करता है। यह उस समय से एक अवधारणा है जब कोई मल्टीटास्किंग ऑपरेटिंग सिस्टम मौजूद नहीं था।
उदाहरण
(विकिपीडिया लेखों से थोड़ा संशोधित)
उदाहरण 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;
}
t = *x
, कॉल करता है swap()
, तो t
ओवरराइड हो जाएगा, जिससे अप्रत्याशित परिणाम हो सकते हैं।
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
को स्टैक पर आवंटित की अपनी कॉपी मिलती है ।
यह परिभाषा पर निर्भर करता है। उदाहरण के लिए Qt निम्नलिखित का उपयोग करता है:
एक थ्रेड-सुरक्षित * फ़ंक्शन को कई थ्रेड्स से एक साथ बुलाया जा सकता है, तब भी जब इनवोकेशन साझा डेटा का उपयोग करते हैं, क्योंकि साझा डेटा के सभी संदर्भ क्रमबद्ध होते हैं।
एक रिपेंटेंट फ़ंक्शन को एक साथ कई थ्रेड्स से भी बुलाया जा सकता है, लेकिन केवल अगर प्रत्येक इनवोकेशन अपने डेटा का उपयोग करता है।
इसलिए, एक थ्रेड-सुरक्षित फ़ंक्शन हमेशा रीएन्स्ट्रेंट होता है, लेकिन एक रेंटेंट फ़ंक्शन हमेशा थ्रेड-सुरक्षित नहीं होता है।
विस्तार से, एक वर्ग को पुनर्वित्त कहा जाता है यदि इसके सदस्य कार्यों को कई थ्रेड्स से सुरक्षित रूप से कहा जा सकता है, जब तक कि प्रत्येक थ्रेड क्लास के एक अलग उदाहरण का उपयोग करता है। कक्षा थ्रेड-सुरक्षित है यदि इसके सदस्य फ़ंक्शन को कई थ्रेड्स से सुरक्षित रूप से कॉल किया जा सकता है, भले ही सभी थ्रेड्स क्लास के एक ही उदाहरण का उपयोग करें।
लेकिन वे भी सावधानी:
नोट: मल्टीथ्रेडिंग डोमेन में शब्दावली पूरी तरह से मानकीकृत नहीं है। POSIX, रीवेंट्रेंट और थ्रेड-सेफ़ की परिभाषाओं का उपयोग करता है जो इसके C API के लिए कुछ अलग हैं। Qt के साथ अन्य ऑब्जेक्ट-ओरिएंटेड C ++ क्लास लाइब्रेरीज़ का उपयोग करते समय, सुनिश्चित करें कि परिभाषाएँ समझ में आती हैं।