2011-11-11 24 views
12

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

मेरे पास एक कक्षा है जिसमें boost :: mutex सदस्य है। समस्या यह है कि म्यूटक्स धारण करते समय कॉलर्स को कक्षा में कॉल की श्रृंखला करने की आवश्यकता हो सकती है। लेकिन मैं म्यूटेक्स पर कक्षा मुक्त शासन के बाहर कोड नहीं देना चाहता हूं।

मैं क्या करना चाहते हैं क्या इस (छद्म कोड) की तरह कुछ है:

class MyClass 
{ 
private: 
    boost::mutex mLock; 
public: 
    boost::scoped_lock& ScopedLock(return ScopedLock(mLock)); 
} 

इस तरह, कॉल कर सकते हैं:

MyClass f; 
if(foo) 
{ 
boost::scoped_lock lock(f->GetScopedLock()); 
f->LockedFunc1(); 
f->LockedFunc2(); 
} 

विचार यह है कि LockedFunc1 और LockedFunc2 लॉक के साथ बुलाया जाएगा। lock के लिए विनाशक f->mLock अनलॉक करेगा।

मैं दो बुनियादी प्रश्न हैं:

1) मैं यह कैसे कर सकते हैं?

2) क्या यह समझदार है?

नोट: यह इसी तरह के नाम से प्रश्न: return a boost::scoped_lock से बिल्कुल अलग है।

+1

मैं इस पर एक अलग मुद्दे की तलाश में ठोकर खाई। लेकिन, मुझे क्या लगता है कि MyClass के लॉक * आउट * को पार करना वास्तव में आप जो करना चाहते हैं उसके विपरीत है। इसके बजाय, आप वास्तव में जो चाहते हैं वह है MyClass पर एक विधि जिसे "ExecuteAtomically" या "ExecuteLocked" कहा जाता है, जिसे आप किसी प्रकार की लैम्ब्डा अभिव्यक्ति को पारित कर सकते हैं जिसमें लॉक होने के संदर्भ में आप जो भी कोड चाहते हैं उसका मूल्यांकन किया जाता है। मैं बूस्ट/सी ++ के साथ ऐसा करने का सबसे अच्छा तरीका जानने की कोशिश कर रहा हूं, लेकिन हो सकता है कि मेरे से कोई भी चालाक हो सकता है। –

+0

@ChrisCleeland: यह समस्या के बारे में सोचने का एक बहुत ही दिलचस्प तरीका है। –

+0

इसके बारे में थोड़ा और सोचा, और मुझे लगता है कि यह किया जा सकता है। "निष्पादन" विधि में निष्पादन जैसे कुछ निष्पादन हो सकता है (boost :: function ) ताकि आप एक मज़ेदार को परिभाषित कर सकें और पहले तर्क के लिए "यह" (या * यह) बांध सकें। यह अनुमान लगाता है कि ठीक से काम करता है (जो मुझे लगता है), यह फॉस्टर के शरीर को स्थानांतरित करने के लिए बूस्ट :: लैम्ब्डा (या नई भाषा सुविधा) को नियोजित करने के लिए बहुत अधिक काम नहीं होना चाहिए ताकि यह कॉल के साथ जगह हो निष्पादन हेतु()। मुझे आशा थी कि मैं इसे आजमाने के लिए अपनी वर्तमान कार्य सूची में एक जगह का पता लगा सकता हूं, लेकिन मैंने नहीं किया है। बाद में कोशिश कर सकते हैं। –

उत्तर

10

मैं यह कैसे कर सकता हूं?

class t_scope_lock { 
public: 
    t_scope_lock(MyClass& myClass); 
    ... 
private: 
    boost::scoped_lock d_lock; 
}; 

और MyClass के लिए इस प्रकार के लिए म्युटेक्स को पहुंच प्रदान करने:

विकल्प 1

एक दृष्टिकोण एक प्रकार है जो एक boost::scoped_lock है बनाने के लिए किया जाएगा। यदि यह कक्षा विशेष रूप से MyClass के लिए लिखी गई है, तो मैं इसे एक आंतरिक कक्षा MyClass::t_scoped_lock के रूप में जोड़ दूंगा।

विकल्प 2

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

वैकल्पिक 3

कभी कभी यह MyClass के लिए एक अमूर्त परत जोड़ने के लिए बेहतर है।

{ 
boost::scoped_lock lock(f->GetScopedLock()); 
f->LockedFunc1(); 
f->LockedFunc2(); 
} 

वैकल्पिक 4

कभी कभी तुम एक और लॉक का उपयोग कर सकते हैं (उदाहरण के लिए: अगर वर्ग जटिल है, यह संभावना एक अच्छा समाधान है क्योंकि आप वेरिएंट जो की तरह लग रही का एक बहुत प्रदान करना होगा नहीं हैआंतरिक व बाह्य)।

वैकल्पिक 5

# 4 के समान, आप कुछ मामलों में एक पुनरावर्ती या ReadWrite लॉक का उपयोग कर सकते हैं।

वैकल्पिक 6

आप चुनिंदा प्रकार के इंटरफ़ेस के कुछ भागों को पहुंच प्रदान करने के एक बंद आवरण प्रकार का उपयोग कर सकते हैं।

class MyClassLockedMutator : StackOnly { 
public: 
    MyClassLockedMutator(MyClass& myClass); 
// ... 
    void LockedFunc1() { this->myClass.LockedFunc1(); } 
    void LockedFunc2() { this->myClass.LockedFunc2(); } 
private: 
    MyClass& myClass; 
    boost::scoped_lock d_lock; // << locks myClass 
}; 

MyClass f; 
MyClassLockedMutator a(f); 

a.LockedFunc1(); 
a.LockedFunc2(); 

क्या यह समझदार है?

ध्यान रखें कि मुझे नहीं पता कि आपके प्रोग्राम की सटीक बाधाएं क्या हैं (इसलिए, एकाधिक विकल्प)।

विकल्प # 1, # 2, # 3, और # 6 में (वर्चुअल) कोई प्रदर्शन ओवरहेड नहीं है, और कई मामलों में मामूली अतिरिक्त जटिलता है। हालांकि, वे एक ग्राहक के लिए वाक्य रचनात्मक रूप से शोर हैं। आईएमओ, मजबूर शुद्धता जो संकलक जांच सकता है (आवश्यकतानुसार) वाक्य रचनात्मक शोर को कम करने से अधिक महत्वपूर्ण है।

विकल्प # 4 और # 5 अतिरिक्त ओवरहेड/विवाद या लॉकिंग/समवर्ती त्रुटियों और बग पेश करने के अच्छे तरीके हैं। कुछ मामलों में, यह विचार करने के लिए एक साधारण प्रतिस्थापन है।

जब शुद्धता, प्रदर्शन, और/या अन्य प्रतिबंध महत्वपूर्ण हैं, तो मुझे लगता है कि यह उन जटिलताओं को अमूर्त या encapsulate करने के लिए सही अर्थ बनाता है, भले ही यह कुछ वाक्य रचनात्मक शोर या एक अमूर्त परत लागत। मैं ऐसा इसलिए करता हूं क्योंकि ब्रेकिंग में बदलाव करना बहुत आसान है - भले ही मैंने पूरे कार्यक्रम को लिखा और बनाए रखा हो। मेरे लिए, यह दृश्यता का एक और विस्तृत मामला है, और सही ढंग से उपयोग किए जाने पर पूरी तरह समझदार है।

कुछ उदाहरण

नीचे स्क्रॉल main करने के लिए - इस नमूने नहीं बल्कि बेतरतीब है, क्योंकि यह एक में कई दृष्टिकोण को दर्शाता है:

#include <iostream> 
#include <boost/thread.hpp> 

class MyClass; 

class MyClassOperatorBase { 
public: 
    /* >> public interface */ 
    bool bazzie(bool foo); 
protected: 
    MyClassOperatorBase(MyClass& myClass) : d_myClass(myClass) { 
    } 

    virtual ~MyClassOperatorBase() { 
    } 

    operator boost::mutex &(); 

    MyClass& getMyClass() { 
     return this->d_myClass; 
    } 

    const MyClass& getMyClass() const { 
     return this->d_myClass; 
    } 

protected: 
    /* >> required overrides */ 
    virtual bool imp_bazzie(bool foo) = 0; 
private: 
    MyClass& d_myClass; 
private: 
    /* >> prohibited */ 
    MyClassOperatorBase(const MyClassOperatorBase&); 
    MyClassOperatorBase& operator=(const MyClassOperatorBase&); 
}; 

class MyClass { 
public: 
    MyClass() : mLock() { 
    } 

    virtual ~MyClass() { 
    } 

    void LockedFunc1() { 
     std::cout << "hello "; 
    } 

    void LockedFunc2() { 
     std::cout << "world\n"; 
    } 

    bool bizzle(bool foo) { 
     boost::mutex::scoped_lock lock(this->mLock); 

     return this->imp_bizzle(foo); 
    } 

protected: 
    virtual bool imp_bizzle(bool foo) { 
     /* would be pure virtual if we did not need to create it for other tests. */ 
     return foo; 
    } 

private: 
    class t_scope_lock { 
    public: 
     t_scope_lock(MyClass& myClass) : d_lock(myClass.mLock) { 
     } 

    private: 
     boost::mutex::scoped_lock d_lock; 
    }; 
protected: 
    friend class MyClassOperatorBase; 
private: 
    boost::mutex mLock; 
}; 

MyClassOperatorBase::operator boost::mutex &() { 
    return this->getMyClass().mLock; 
} 

bool MyClassOperatorBase::bazzie(bool foo) { 
    MyClass::t_scope_lock lock(this->getMyClass()); 

    return this->imp_bazzie(foo); 
} 

class TheirClassOperator : public MyClassOperatorBase { 
public: 
    TheirClassOperator(MyClass& myClass) : MyClassOperatorBase(myClass) { 
    } 

    virtual ~TheirClassOperator() { 
    } 

    bool baz(bool foo) { 
     boost::mutex::scoped_lock lock(*this); 

     return this->work(foo); 
    } 

    boost::mutex& evilClientMove() { 
     return *this; 
    } 

protected: 
    virtual bool imp_bazzie(bool foo) { 
     return this->work(foo); 
    } 

private: 
    bool work(bool foo) { 
     MyClass& m(this->getMyClass()); 

     m.LockedFunc1(); 
     m.LockedFunc2(); 
     return foo; 
    } 
}; 

class TheirClass : public MyClass { 
public: 
    TheirClass() : MyClass() { 
    } 

    virtual ~TheirClass() { 
    } 

protected: 
    virtual bool imp_bizzle(bool foo) { 
     std::cout << "hallo, welt!\n"; 
     return foo; 
    } 
}; 

namespace { 
/* attempt to restrict the lock's visibility to MyClassOperatorBase types. no virtual required: */ 
void ExampleA() { 
    MyClass my; 
    TheirClassOperator their(my); 

    their.baz(true); 

// boost::mutex::scoped_lock lock(my); << error inaccessible 
// boost::mutex::scoped_lock lock(my.mLock); << error inaccessible 
// boost::mutex::scoped_lock lock(their); << error inaccessible 

    boost::mutex::scoped_lock lock(their.evilClientMove()); 
} 

/* restrict the lock's visibility to MyClassOperatorBase and call through a virtual: */ 
void ExampleB() { 
    MyClass my; 
    TheirClassOperator their(my); 

    their.bazzie(true); 
} 

/* if they derive from my class, then life is simple: */ 
void ExampleC() { 
    TheirClass their; 

    their.bizzle(true); 
} 
} 

int main(int argc, const char* argv[]) { 
    ExampleA(); 
    ExampleB(); 
    ExampleC(); 
    return 0; 
} 
+0

मुझे यह देखना होगा कि मैं यह काम कर सकता हूं या नहीं। मुझे यकीन नहीं है कि t_scope_lock कन्स्ट्रक्टर को लॉक तक पहुंच कैसे मिलेगी। क्या कक्षाओं को किसी प्रकार की 't_scope_lockable' कक्षा से उतरना होगा ताकि t_scope_lock कन्स्ट्रक्टर * उनके लॉक को ढूंढ सके? (यदि कॉलर्स को प्रश्न में साफ वाक्यविन्यास मिलता है तो मैं कक्षाओं में कुछ कुरूपता के साथ रह सकता हूं।) –

+0

इसके अलावा, यदि आप संभव हो तो प्रश्न 2 का उत्तर दे सकते हैं? हर कोई ऐसा क्यों नहीं करता? ऐसा लगता है कि एक कक्षा पर काम करने का एक प्राकृतिक तरीका है जिसे कई परिचालनों पर स्थिर होना है। (एक्सेसर ऑब्जेक्ट प्राप्त करें। इस पर जानकारी एक्स और वाई जांचें, निर्णय लें, एक्सेसर के माध्यम से ऑब्जेक्ट को संशोधित करें। हो गया, लॉक रिलीज करें।) क्या मैं अपने बारे में सोच रहा हूं (बहुत सामान्य, मुझे लगता है) समस्या गलत है? या क्या हर कोई यह कुछ भयानक तरीका करता है? –

+0

@ डेविड री टिप्पणी # 1: मैंने प्रतिक्रिया का विस्तार किया है (जोड़ा गया alt.4-6), लेकिन मैं एक नमूना तैयार करूंगा। पुनः टिप्पणी # 2: क्षमा करें, उस समय तक उस बिट को भूल गए जब मैंने पहले जवाब दिया - प्रतिक्रिया अद्यतन।मैं शुद्धता और गति सुनिश्चित करने के लिए और भी अधिक लंबाई तक जाऊंगा, जिससे कंपाइलर कुछ चेक कर सकता है (प्रोग्राम बहुत बड़े हैं और खुद को भरोसा करने के लिए बहुत जल्दी अपडेट होते हैं)। मुझे यकीन नहीं है कि मैंने इसे अक्सर क्यों नहीं देखा है - शायद क्योंकि इसे लागू करने के लिए समय लगता है, और ग्राहकों के उपयोग के लिए। मेरे लिए, इसे अधिक जटिल मामलों को अमूर्त करने के लिए कम समय (समग्र) की आवश्यकता होती है। – justin

0

पसंदीदा समाधान इस तरह एक परमाणु समारोह होगा:

void MyClass::Func_1_2(void) 
{ 
    boost::lock_guard<boost::mutex> lock(m_mutex); 
    LockedFunc1(); 
    LockedFunc2(); 
} 

आपको इनमें से कई अतिरिक्त विधियां प्रदान करनी पड़ सकती हैं। सिद्धांत: क्या उपयोगकर्ता से आपकी लॉकिंग नीतियों को छिपाना बेहतर है। यदि आपको लगता है कि विशेष तरीकों को बनाना उचित नहीं है, तो आप अपने डिजाइन, सार को उच्च स्तर पर पुनर्विचार करना चाहेंगे।

यदि आपके पास इंटरफ़ेस को समान रखने के वैध कारण हैं, तो सहायक वर्गों के पीछे लॉकिंग विवरण छुपाएं। दो उदाहरण

लॉक की आवश्यकता वाले विधियों को पारित टोकन क्लास के पीछे लॉक छुपाएं।

MyClass my_class; 

{ 
    LockedMyClass locked(my_class); 
    locked.Func1(); 
    locked.Func2(); 
} 

इस समझदार है:

MyClass my_class; 

{ 
    LockMyClass locked(my_class); 
    myclass.Func1(locked); // assert or throw if locked is not locking my_class 
    myclass.Func2(locked); 
} 

एक बंद इंटरफ़ेस वर्ग MyClass के मित्र हैं कि बनाएँ?

यदि आप सावधान हैं तो यह किया जा सकता है लेकिन आम तौर पर आप अपनी कक्षा के बाहर अपने सिंक्रनाइज़ेशन विवरण का खुलासा नहीं करना चाहते हैं। ऐसी कई समस्याएं उत्पन्न हो सकती हैं जो उत्पन्न हो सकती हैं। सूर्य ने java.util.Vector के साथ एक समान विचार की कोशिश की, लेकिन तब से बेहतर तकनीक पर चले गए हैं।

+0

आपके पास एक वर्ग है जो संग्रह का प्रतिनिधित्व करता है और उस वर्ग के सभी ग्राहकों को उस संग्रह के सदस्यों पर बहुत ही जटिल संचालन करने की आवश्यकता होती है (एक्स खोजें, वाई ढूंढें, निर्णय लें, एक्स संशोधित करें, वाई हटाएं .. ।) और आपको अधिक समेकन की आवश्यकता नहीं है, इसलिए संग्रह पर एक ही लॉक पर्याप्त है? –

+0

क्या आप वर्णन करते हैं कि लॉक की तरह लगता है कंटेनर के बाहर मौजूद होना चाहिए। तो हाँ एक ताला सही लगता है। या आप कंटेनर 'MyClass :: make_decision (x, y)' में एक विधि जोड़ने में सक्षम हो सकते हैं जो पूरी प्रक्रिया को संभालेगा। –

+0

ठीक है, लॉक कंटेनर के बाहर मौजूद है। अब, मुझे एक ही समस्या है, बस "कंटेनर" अब "जहां भी ताला मौजूद है" है। कक्षा निर्णय नहीं ले सकती क्योंकि इसे कक्षा के दायरे से बाहर जानकारी के रास्ते तक लगातार पहुंच की आवश्यकता होती है। (कई स्तरों के संबंधित राज्यों पर संचालित कई परिष्कृत कॉलिंग कक्षाओं द्वारा संचालित निम्न स्तर की 'ऑब्जेक्ट स्टेट' कक्षा और 'ऑब्जेक्ट स्टेटस का संग्रह' वर्ग पर विचार करें। कंटेनर का उद्देश्य विशेष रूप से संचालन में एक सतत राज्य को लागू करना है जब तक कॉलर समाप्त नहीं हो जाता है।) –

0

इस प्रकार मैं वर्तमान में इसे करने की योजना बना रहा हूं। मैं ScopedLock कक्षा बनाउंगा जिसे वापस किया जा सकता है। इसका उपयोग करने के लिए एक कक्षा में boost::mutex होना चाहिए और उस म्यूटेक्स के साथ निर्मित ScopedLock होना चाहिए। कॉलर फ़ंक्शन का उपयोग अपने स्वयं के स्कोप्ड लॉक बनाने के लिए करता है, और कॉलर के स्कोप्ड लॉक को क्लास सदस्य फ़ंक्शन द्वारा बनाए गए लॉक को प्राप्त होता है।

सूचक सुरक्षित है क्योंकि स्कोप्ड लॉक वर्ग सदस्य के जीवन से अधिक नहीं हो सकता है जिसका सदस्य कार्य आपको इसे प्राप्त करने के लिए बुलाया जाता है। और आपको गारंटी है (कक्षा के तर्क से) कि केवल एक अनलॉक होगा।

एकमात्र असली समस्या जो मैं देखता हूं वह जानबूझकर दुर्व्यवहार होगा। उदाहरण के लिए, अगर किसी ने अपने स्कोप्ड लॉक से एक नया स्कोप्ड लॉक बनाया है, तो अन्य स्कोप्ड लॉक (संभवतः लंबे जीवन के साथ) को लॉक के उत्तराधिकारी के कारण यह नहीं होना चाहिए। (यह दर्द होता है जब मैं ऐसा। तो ऐसा नहीं है।)

class ScopedLock { 
private: 
    boost::mutex *mMutex; // parent object has greater scope, so guaranteed valid 
    mutable bool mValid; 

    ScopedLock();   // no implementation 

public: 
    ScopedLock(boost::mutex &mutex) : mMutex(&mutex), mValid(true) { 
     mMutex->lock(); 
    } 

    ~ScopedLock() { 
     if(mValid) mMutex->unlock(); 
    } 

    ScopedLock(const ScopedLock &sl) { 
     mMutex=sl.mMutex; 
     if(sl.mValid) 
     { 
       mValid=true; 
       sl.mValid=false; 
     } 
     else mValid=false; 
    } 

    ScopedLock &operator=(const ScopedLock &sl) 
    { // we inherit any lock the other class member had 
     if(mValid) mMutex->unlock(); 
     mMutex=sl.mMutex; 
     if(sl.mValid) { 
      mValid=true; 
      sl.mValid=false; 
     } 
    } 
}; 

यह अभी भी मेरे लिए एक तरह से गलत लगता है। मैंने सोचा कि बूस्ट का पूरा बिंदु उन सभी चीजों के लिए एक साफ इंटरफेस प्रदान करना था जो आपको करने की सबसे अधिक संभावना है। ऐसा लगता है कि बहुत मेरे लिए नियमित है। और तथ्य यह है कि ऐसा करने का कोई साफ तरीका मुझे डराता है।

अद्यतन: ऐसा करने के लिए "सही" बूस्ट तरीका लॉक धारक ऑब्जेक्ट में shared_ptr का उपयोग करना है। ऑब्जेक्ट दूर हो जाएगा जब उसका आखिरी सूचक नष्ट हो जाएगा, लॉक जारी करेगा।

+0

क्या आप ऐसा करने के सही बढ़ावा देने के तरीके को दर्शाने के लिए अपना उदाहरण अपडेट कर सकते हैं? मैं इस पर पूरी तरह से स्पष्ट नहीं हूँ। –

संबंधित मुद्दे