2016-04-14 8 views
8

में एक साझा रिकर्सिव म्यूटेक्स सी ++ 17 के लिए shared_mutex वर्ग की योजना बनाई गई है। और shared_timed_mutex पहले से ही सी ++ 14 में है। (कौन जानता है कि वे उस क्रम में क्यों आए, लेकिन जो भी हो।) फिर C12+ 11 के बाद recursive_mutex और recursive_timed_mutex है। मुझे क्या चाहिए shared_recursive_mutex। क्या मुझे मानक में कुछ याद आया या क्या मुझे इसके मानकीकृत संस्करण के लिए एक और तीन साल इंतजार करना पड़ा?मानक सी ++

यदि वर्तमान में ऐसी कोई सुविधा नहीं है, तो मानक सी ++ का उपयोग करके ऐसी सुविधा का एक सरल (पहली प्राथमिकता) और कुशल (दूसरी प्राथमिकता) कार्यान्वयन क्या होगी?

+0

मैं ऐसी चीज लिखने की कोशिश नहीं करता। लॉक रिकर्सिव बनाने के बारे में कैसे? –

+1

एक रिकर्सिव म्यूटेक्स की आवश्यकता आमतौर पर एक संकेत है कि कोड को फिर से डिजाइन करने की आवश्यकता है। –

उत्तर

3

रिकर्सिव संपत्ति अवधि मालिक, जो shared_mutex के मामले में नहीं अच्छी तरह से परिभाषित किया गया है के साथ चल रही है। (! नहीं .lock_shared())

एक धागा जो कहता है .lock() रूप मालिक मानते हुए, पुनरावर्ती साझा म्युटेक्स के कार्यान्वयन बस shared_mutex से प्राप्त किया जा सकता है:

class shared_recursive_mutex: public shared_mutex 
{ 
public: 
    void lock(void) { 
     std::thread::id this_id = std::this_thread::get_id(); 
     if(owner == this_id) { 
      // recursive locking 
      count++; 
     } 
     else { 
      // normal locking 
      shared_mutex::lock(); 
      owner = this_id; 
      count = 1; 
     } 
    } 
    void unlock(void) { 
     if(count > 1) { 
      // recursive unlocking 
      count--; 
     } 
     else { 
      // normal unlocking 
      owner = std::thread::id(); 
      count = 0; 
      shared_mutex::unlock(); 
     } 
    } 

private: 
    std::atomic<std::thread::id> owner; 
    int count; 
}; 

फील्ड .owner जरूरत, परमाणु के रूप में घोषित किए जाने की में क्योंकि .lock() विधि समवर्ती पहुंच से सुरक्षा के बिना इसे चेक किया गया है।

आप रिकर्सिवली .lock_shared() विधि कॉल करना चाहते हैं, तो आप मालिकों की नक्शा बनाए रखने की जरूरत है, और कुछ अतिरिक्त म्युटेक्स साथ रक्षा की जानी चाहिए कि नक्शे को एक्सेस करता है।

सक्रिय .lock() के साथ .lock_shared() कॉल करने के लिए थ्रेड को आवंटित करना अधिक जटिल बनाते हैं।के रूप में यह संभव गतिरोध की ओर जाता है जब दो धागे को आगे बढ़ाने कि प्रदर्शन करने के लिए प्रयास

अंत में, .lock_shared() से .lock() करने के लिए अग्रिम ताला धागा की इजाजत दी नहीं-नहीं है।


फिर, पुनरावर्तीका अर्थ साझा म्युटेक्स, बहुत नाजुक हो जाएगा तो यह बेहतर है सब पर उसका उपयोग नहीं करने के लिए।

template<class T, class Lock> 
struct lock_guarded { 
    Lock l; 
    T* t; 
    T* operator->()&&{ return t; } 
    template<class Arg> 
    auto operator[](Arg&&arg)&& 
    -> decltype(std::declval<T&>()[std::declval<Arg>()]) 
    { 
    return (*t)[std::forward<Arg>(arg)]; 
    } 
    T& operator*()&&{ return *t; } 
}; 
constexpr struct emplace_t {} emplace {}; 
template<class T> 
struct mutex_guarded { 
    lock_guarded<T, std::unique_lock<std::mutex>> 
    get_locked() { 
    return {{m},&t}; 
    } 
    lock_guarded<T const, std::unique_lock<std::mutex>> 
    get_locked() const { 
    return {{m},&t}; 
    } 
    lock_guarded<T, std::unique_lock<std::mutex>> 
    operator->() { 
    return get_locked(); 
    } 
    lock_guarded<T const, std::unique_lock<std::mutex>> 
    operator->() const { 
    return get_locked(); 
    } 
    template<class F> 
    std::result_of_t<F(T&)> 
    operator->*(F&& f) { 
    return std::forward<F>(f)(*get_locked()); 
    } 
    template<class F> 
    std::result_of_t<F(T const&)> 
    operator->*(F&& f) const { 
    return std::forward<F>(f)(*get_locked()); 
    } 
    template<class...Args> 
    mutex_guarded(emplace_t, Args&&...args): 
    t(std::forward<Args>(args)...) 
    {} 
    mutex_guarded(mutex_guarded&& o): 
    t(std::move(*o.get_locked())) 
    {} 
    mutex_guarded(mutex_guarded const& o): 
    t(*o.get_locked()) 
    {} 
    mutex_guarded() = default; 
    ~mutex_guarded() = default; 
    mutex_guarded& operator=(mutex_guarded&& o) 
    { 
    T tmp = std::move(o.get_locked()); 
    *get_locked() = std::move(tmp); 
    return *this; 
    } 
    mutex_guarded& operator=(mutex_guarded const& o): 
    { 
    T tmp = o.get_locked(); 
    *get_locked() = std::move(tmp); 
    return *this; 
    } 

private: 
    std::mutex m; 
    T t; 
}; 

आप उपयोग कर सकते हैं:

+0

हम्म, "अगर किसी थ्रेड द्वारा लॉक_श्रेड को बुलाया जाता है जो पहले से ही किसी भी मोड (अनन्य या साझा) में म्यूटेक्स का मालिक है, तो व्यवहार अपरिभाषित है।" - यह कष्टप्रद है। – Yakk

+0

@Yakk: यह सामान्य 'shared_mutex' के लिए निर्दिष्ट है। * रिकर्सिव * संपत्ति निश्चित रूप से म्यूटेक्स अर्थपूर्ण बदल जाएगी, इसलिए म्यूटेक्स की अलग-अलग आवश्यकताएं होंगी। – Tsyvarev

2

यदि आप लिनक्स/पॉज़िक्स प्लेटफ़ॉर्म पर हैं, तो आप भाग्यशाली हैं क्योंकि C++ म्यूटेक्स को पॉज़िक्स वाले के बाद मॉडलिंग किया जाता है। POSIX वाले लोग रिकर्सिव, प्रक्रिया साझा और अधिक सहित अधिक सुविधाएं प्रदान करते हैं। और सी ++ कक्षाओं में पॉज़िक्स प्राइमेटिव्स को लपेटना सीधे आगे है।

Good entry point into POSIX threads documentation

+4

"मानक सी ++ का उपयोग कर ऐसी सुविधा का?" आप बिंदु खो रहे हैं। आप देशी एपीआई के साथ कुछ भी लिख सकते हैं, लेकिन ओपी स्पष्ट रूप से मानक –

+0

@ डेविडहाइम चाहता है दुर्भाग्यवश म्यूटेक्स को थ्रेड को म्यूटेक्स प्रतीक्षा कतार में रखने के लिए ओएस के साथ बातचीत की आवश्यकता होती है, जिसे कर्नेल द्वारा बनाए रखा जाता है। –

+0

ताकि आपके उत्तर के बजाय आपका जवाब होना चाहिए, "आप नहीं कर सकते" –

1

यह मौजूदा प्राइमेटिव का उपयोग करके एक साझा रिकर्सिव म्यूटेक्स बनाने के लिए संभव है। हालांकि, मैं इसे करने की सिफारिश नहीं करता हूं।

यह आसान नहीं है, और मौजूदा POSIX कार्यान्वयन (या जो भी आपके प्लेटफ़ॉर्म के मूल निवासी है) को लपेटना बहुत अधिक कुशल होने की संभावना है।

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

मैं निश्चित रूप से एक नमूना रिकर्सिव पढ़ने/लिखने के लॉक कार्यान्वयन प्रदान नहीं कर रहा हूं, क्योंकि यह एक स्टैक ओवरफ़्लो उत्तर के लिए पूरी तरह से अनुचित मात्रा में काम है। कई धागे .lock_shared() एक ही समय में कहा जाता है हो सकता है: म्युटेक्स की

2

यहाँ एक प्रकार टी के चारों ओर एक त्वरित धागे की सुरक्षा आवरण है

mutex_guarded<std::vector<int>> guarded; 
auto s0 = guarded->size(); 
auto s1 = guarded->*[](auto&&e){return e.size();}; 

दोनों लगभग एक ही बात करते हैं, और वस्तु पहरा केवल पहुँचा जा सकता है जब म्यूटेक्स बंद हो जाता है।

@tsyvarev के जवाब (कुछ मामूली परिवर्तन के साथ) से चोरी हम पाते हैं:

class shared_recursive_mutex 
{ 
    std::shared_mutex m 
public: 
    void lock(void) { 
    std::thread::id this_id = std::this_thread::get_id(); 
    if(owner == this_id) { 
     // recursive locking 
     ++count; 
    } else { 
     // normal locking 
     m.lock(); 
     owner = this_id; 
     count = 1; 
    } 
    } 
    void unlock(void) { 
    if(count > 1) { 
     // recursive unlocking 
     count--; 
    } else { 
     // normal unlocking 
     owner = std::thread::id(); 
     count = 0; 
     m.unlock(); 
    } 
    } 
    void lock_shared() { 
    std::thread::id this_id = std::this_thread::get_id(); 
    if (shared_counts->count(this_id)) { 
     ++(shared_count.get_locked()[this_id]); 
    } else { 
     m.lock_shared(); 
     shared_count.get_locked()[this_id] = 1; 
    } 
    } 
    void unlock_shared() { 
    std::thread::id this_id = std::this_thread::get_id(); 
    auto it = shared_count->find(this_id); 
    if (it->second > 1) { 
     --(it->second); 
    } else { 
     shared_count->erase(it); 
     m.unlock_shared(); 
    } 
    } 
private: 
    std::atomic<std::thread::id> owner; 
    std::atomic<std::size_t> count; 
    mutex_guarded<std::map<std::thread::id, std::size_t>> shared_counts; 
}; 

try_lock और try_lock_shared एक व्यायाम के रूप में छोड़ दिया।

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


उपरोक्त एक विशेष लॉक, फिर साझा लॉक रखने का समर्थन नहीं करता है। यह मुश्किल है। यह एक साझा लॉक रखने का समर्थन नहीं कर सकता है, फिर एक अद्वितीय लॉक में अपग्रेड कर सकता है, क्योंकि यह 2 धागे की कोशिश करते समय इसे डेडलॉकिंग से रोकने के लिए मूल रूप से असंभव है।

वह अंतिम मुद्दा हो सकता है कि साझा म्यूटेक्स एक बुरा विचार क्यों है।

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