1. कैसे सुरक्षित है रूप परिभाषित किया गया है?
शब्दार्थ। इस मामले में, यह एक कठिन परिभाषित शब्द नहीं है। इसका मतलब सिर्फ यह है कि "आप ऐसा कर सकते हैं, जोखिम के बिना"।
2. यदि किसी कार्यक्रम को सुरक्षित रूप से समवर्ती रूप से निष्पादित किया जा सकता है, तो क्या इसका अर्थ हमेशा यह होता है कि वह प्रतिशोधी है?
नहीं।
उदाहरण के लिए, हमारे पास C ++ फ़ंक्शन है जो एक लॉक और पैरामीटर के रूप में कॉलबैक दोनों लेता है:
#include <mutex>
typedef void (*callback)();
std::mutex m;
void foo(callback f)
{
m.lock();
// use the resource protected by the mutex
if (f) {
f();
}
// use the resource protected by the mutex
m.unlock();
}
एक अन्य फ़ंक्शन को समान म्यूटेक्स को लॉक करने की आवश्यकता हो सकती है:
void bar()
{
foo(nullptr);
}
पहली नजर में, सब कुछ ठीक लगता है ... लेकिन रुकिए:
int main()
{
foo(bar);
return 0;
}
यदि म्यूटेक्स पर ताला पुनरावर्ती नहीं है, तो यहां मुख्य धागा में क्या होगा:
main
बुला लेंगे foo
।
foo
ताला का अधिग्रहण करेगा।
foo
बुलाएगा bar
, जो बुलाएगाfoo
।
- दूसरा
foo
लॉक प्राप्त करने का प्रयास करेगा, विफल हो जाएगा और इसके जारी होने की प्रतीक्षा करेगा।
- गतिरोध।
- ओह ...
ठीक है, मैंने कॉलबैक चीज़ का उपयोग करके धोखा दिया। लेकिन समान प्रभाव वाले कोड के अधिक जटिल टुकड़ों की कल्पना करना आसान है।
3. छः बिंदुओं के बीच सामान्य धागा क्या है, जिसका मुझे ध्यान रखना चाहिए कि मैं रीएन्ट्रेंट क्षमताओं के लिए अपने कोड की जांच कर रहा हूं?
आप एक समस्या को सूँघ सकते हैं यदि आपके फ़ंक्शन में एक परिवर्तनीय स्थायी संसाधन तक पहुँच है / है, या एक ऐसे फ़ंक्शन तक पहुँच देता है, जिसमें बदबू आती है ।
( ठीक है, हमारे कोड के 99% को गंध चाहिए, फिर ... इसे संभालने के लिए अंतिम अनुभाग देखें ... )
इसलिए, अपने कोड का अध्ययन करते हुए, उन बिंदुओं में से एक को आपको सचेत करना चाहिए:
- फ़ंक्शन में एक स्थिति होती है (जैसे वैश्विक चर, या यहां तक कि एक वर्ग सदस्य चर तक पहुंच)
- इस फ़ंक्शन को कई थ्रेड्स द्वारा बुलाया जा सकता है, या स्टैक में दो बार दिखाई दे सकता है जबकि प्रक्रिया निष्पादित हो रही है (यानी फ़ंक्शन खुद को, प्रत्यक्ष या अप्रत्यक्ष रूप से कॉल कर सकता है)। पैरामीटर के रूप में कॉलबैक लेने के कार्य में बहुत अधिक गंध आती है ।
ध्यान दें कि गैर-पुनर्संयोजन वायरल है: एक फ़ंक्शन जो एक संभावित गैर-रीवेंट्रेंट फ़ंक्शन को कॉल कर सकता है, उसे रीएन्ट्रेंट नहीं माना जा सकता है।
ध्यान दें, भी, कि C ++ विधियाँ सूंघती हैं क्योंकि उनकी पहुँच होती है this
, इसलिए आपको यह सुनिश्चित करने के लिए कोड का अध्ययन करना चाहिए कि उनके पास कोई मज़ेदार बातचीत नहीं है।
4.1। क्या सभी पुनरावर्ती कार्य हैं?
नहीं।
मल्टीथ्रेडेड मामलों में, एक साझा संसाधन तक पहुंचने वाले एक पुनरावर्ती कार्य को एक ही समय में कई थ्रेड्स द्वारा बुलाया जा सकता है, जिसके परिणामस्वरूप खराब / दूषित डेटा हो सकता है।
एकलथ्रेडेड मामलों में, एक पुनरावर्ती फ़ंक्शन एक गैर-रीवेंट्रेंट फ़ंक्शन (जैसे कुख्यात strtok
) का उपयोग कर सकता है, या इस तथ्य को संभालने के बिना वैश्विक डेटा का उपयोग कर सकता है कि डेटा पहले से उपयोग में है। तो आपका कार्य पुनरावर्ती है क्योंकि यह स्वयं को प्रत्यक्ष या अप्रत्यक्ष रूप से कहता है, लेकिन यह अभी भी पुनरावर्ती-असुरक्षित हो सकता है ।
4.2। क्या सभी थ्रेड-सेफ फंक्शन रीवेंटेंट हैं?
ऊपर दिए गए उदाहरण में, मैंने दिखाया कि कैसे एक जाहिरा तौर पर थ्रेड्सफ़ेफ़ फ़ंक्शन रेंटेंट नहीं था। ठीक है, मैंने कॉलबैक पैरामीटर के कारण धोखा दिया। लेकिन फिर, एक गैर-पुनरावर्ती लॉक को दो बार हासिल करने से एक धागे को गतिरोध करने के कई तरीके हैं।
4.3। क्या सभी पुनरावर्ती और थ्रेड-सुरक्षित फ़ंक्शन रीइंटरेंट हैं?
मैं "हाँ" कहूँगा यदि "पुनरावर्ती" से आपका मतलब है "पुनरावर्ती-सुरक्षित"।
यदि आप यह गारंटी दे सकते हैं कि एक फ़ंक्शन को कई थ्रेड्स द्वारा एक साथ कॉल किया जा सकता है, और खुद को, प्रत्यक्ष या अप्रत्यक्ष रूप से, बिना किसी समस्या के कॉल कर सकते हैं, तो यह रीट्रेन्ट है।
समस्या इस गारंटी का मूल्यांकन कर रही है ... ^ _ ^
5. क्या श्रद्धा और धागा सुरक्षा जैसी शर्तें बिल्कुल सही हैं, यानी क्या उनकी कोई ठोस परिभाषा है?
मेरा मानना है कि वे ऐसा करते हैं, लेकिन फिर, किसी फ़ंक्शन का मूल्यांकन करना थ्रेड-सेफ है या रीएंट्रेंट मुश्किल हो सकता है। यही कारण है कि मैंने गंध शब्द का इस्तेमाल किया ऊपर है: आप पा सकते हैं कि एक फ़ंक्शन रीएन्ट्रेंट नहीं है, लेकिन यह सुनिश्चित करना मुश्किल हो सकता है कि कोड का एक जटिल टुकड़ा रीएन्ट्रेंट है
6. एक उदाहरण
मान लें कि आपके पास एक ऑब्जेक्ट है, जिसमें एक विधि है जिसे संसाधन का उपयोग करने की आवश्यकता है:
struct MyStruct
{
P * p;
void foo()
{
if (this->p == nullptr)
{
this->p = new P();
}
// lots of code, some using this->p
if (this->p != nullptr)
{
delete this->p;
this->p = nullptr;
}
}
};
पहली समस्या यह है कि अगर किसी तरह इस फ़ंक्शन को पुनरावर्ती कहा जाता है (यानी यह फ़ंक्शन खुद को प्रत्यक्ष या अप्रत्यक्ष रूप से कहता है), तो कोड संभवतः दुर्घटनाग्रस्त हो जाएगा, क्योंकि this->p
जाएगा अंतिम कॉल के अंत में हटा दिया जाएगा, और अभी भी संभवतः समाप्ति से पहले उपयोग किया जाएगा पहली कॉल की।
इस प्रकार, यह कोड पुनरावर्ती-सुरक्षित नहीं है ।
हम इसे ठीक करने के लिए एक संदर्भ काउंटर का उपयोग कर सकते हैं:
struct MyStruct
{
size_t c;
P * p;
void foo()
{
if (c == 0)
{
this->p = new P();
}
++c;
// lots of code, some using this->p
--c;
if (c == 0)
{
delete this->p;
this->p = nullptr;
}
}
};
इस तरह, कोड पुनरावर्ती-सुरक्षित हो जाता है ... लेकिन यह अभी भी है क्योंकि मुद्दों multithreading के रैत्रांत नहीं है: हम होना चाहिए यकीन है कि के संशोधन c
और की p
atomically किया जाएगा, एक का उपयोग कर पुनरावर्ती म्युटेक्स (सभी नहीं mutexes पुनरावर्ती हैं):
#include <mutex>
struct MyStruct
{
std::recursive_mutex m;
size_t c;
P * p;
void foo()
{
m.lock();
if (c == 0)
{
this->p = new P();
}
++c;
m.unlock();
// lots of code, some using this->p
m.lock();
--c;
if (c == 0)
{
delete this->p;
this->p = nullptr;
}
m.unlock();
}
};
और निश्चित रूप से, यह सभी मानता है कि lots of code
इसका उपयोग करने सहित, स्वयं ही प्रतिरूप हैp
।
और ऊपर दिया गया कोड दूरस्थ रूप से अपवाद-सुरक्षित भी नहीं है , लेकिन यह एक और कहानी है ... ^ _ ^
7. अरे 99% हमारा कोड रीएन्स्ट्रेंट नहीं है!
स्पेगेटी कोड के लिए यह काफी सही है। लेकिन अगर आप अपने कोड को सही ढंग से विभाजित करते हैं, तो आप रीटर्रेंस की समस्याओं से बच जाएंगे।
7.1। सुनिश्चित करें कि सभी कार्यों में कोई स्थिति नहीं है
उन्हें केवल मापदंडों, अपने स्वयं के स्थानीय चर, राज्य के बिना अन्य कार्यों का उपयोग करना चाहिए, और यदि वे बिल्कुल भी वापस आते हैं तो डेटा की प्रतियां लौटाएं।
7.2। सुनिश्चित करें कि आपकी वस्तु "पुनरावर्ती-सुरक्षित" है
एक ऑब्जेक्ट विधि तक पहुंच है this
, इसलिए यह ऑब्जेक्ट के एक ही उदाहरण के सभी तरीकों के साथ एक राज्य साझा करता है।
इसलिए, सुनिश्चित करें कि स्टैक (यानी कॉलिंग ए विधि) में एक बिंदु पर ऑब्जेक्ट का उपयोग किया जा सकता है, और फिर, पूरे ऑब्जेक्ट को दूषित किए बिना, दूसरे बिंदु पर (यानी कॉलिंग बी)। अपनी वस्तु को यह सुनिश्चित करने के लिए डिज़ाइन करें कि किसी विधि से बाहर निकलने पर, वस्तु स्थिर और सही है (कोई झूलने वाले बिंदु नहीं, सदस्य चर का कोई विरोधाभास नहीं, आदि)।
7.3। सुनिश्चित करें कि आपकी सभी वस्तुएँ सही ढंग से संलग्न हैं
किसी और को अपने आंतरिक डेटा तक पहुंच नहीं होनी चाहिए:
// bad
int & MyObject::getCounter()
{
return this->counter;
}
// good
int MyObject::getCounter()
{
return this->counter;
}
// good, too
void MyObject::getCounter(int & p_counter)
{
p_counter = this->counter;
}
यदि उपयोगकर्ता द्वारा डेटा के पते को पुनः प्राप्त किया जाता है, तो भी एक कॉस्ट संदर्भ लौटाना खतरनाक हो सकता है, क्योंकि कोड के कुछ अन्य हिस्से को कॉर्ड संदर्भ को बताए बिना कोड को संशोधित कर सकता है।
7.4। सुनिश्चित करें कि उपयोगकर्ता जानता है कि आपकी वस्तु थ्रेड-सुरक्षित नहीं है
इस प्रकार, उपयोगकर्ता थ्रेड के बीच साझा की गई वस्तु का उपयोग करने के लिए म्यूटेक्स का उपयोग करने के लिए जिम्मेदार है।
STL से ऑब्जेक्ट को थ्रेड-सुरक्षित (प्रदर्शन समस्याओं के कारण) डिज़ाइन किया गया है, और इस प्रकार, यदि कोई उपयोगकर्ता साझा करना चाहता है std::string
दो थ्रेड्स के बीच , तो उपयोगकर्ता को कॉन्सिक्वेंसी प्राइमेटिक्स के साथ इसकी पहुंच की सुरक्षा करनी चाहिए;
7.5। सुनिश्चित करें कि आपका थ्रेड-सुरक्षित कोड पुनरावर्ती-सुरक्षित है
इसका मतलब है कि यदि आप एक ही संसाधन को एक ही धागे से दो बार उपयोग कर सकते हैं, तो पुनरावर्ती म्यूटेक्स का उपयोग करें।