2017-08-03 9 views
15

एक नई प्रयोगात्मक सुविधा (शायद सी ++ 20) है, जो "सिंक्रनाइज़ ब्लॉक" है। ब्लॉक कोड के एक खंड पर एक वैश्विक ताला प्रदान करता है। निम्नलिखित cppreference से एक उदाहरण है।सी ++ में नई सुविधा, "सिंक्रनाइज़" ब्लॉक का क्या लाभ है?

#include <iostream> 
#include <vector> 
#include <thread> 
int f() 
{ 
    static int i = 0; 
    synchronized { 
     std::cout << i << " -> "; 
     ++i;  
     std::cout << i << '\n'; 
     return i; 
    } 
} 
int main() 
{ 
    std::vector<std::thread> v(10); 
    for(auto& t: v) 
     t = std::thread([]{ for(int n = 0; n < 10; ++n) f(); }); 
    for(auto& t: v) 
     t.join(); 
} 

मुझे लगता है यह ज़रूरत से ज़्यादा है।

std::mutex m; 
int f() 
{ 
    static int i = 0; 
    std::lock_guard<std::mutex> lg(m); 
    std::cout << i << " -> "; 
    ++i;  
    std::cout << i << '\n'; 
    return i; 
} 

केवल लाभ मैं यहाँ लगता है कि मैं एक वैश्विक ताला की मेहनत नहीं बचाया रहा हूँ है: ऊपर से एक तुल्यकालन ब्लॉक के बीच कोई अंतर है, और इस से एक है। सिंक्रनाइज़ ब्लॉक का उपयोग करने के और फायदे हैं? इसे कब प्राथमिकता दी जानी चाहिए?

+0

सुनिश्चित नहीं है कि यह वास्तव में मामला है लेकिन cppreference इसे पहले संस्करण की तरह ध्वनि बनाता है, उदाहरण के क्रम में प्रिंट करने की गारंटी है जबकि AFAIK दूसरा संस्करण नहीं है। – NathanOliver

+0

@NathanOliver क्रम में पहला संस्करण प्रिंट क्यों नहीं करेगा? क्या आप समझाएँगे? जहां तक ​​मुझे समझा गया, कोड का पूरा सेट एक समय में केवल एक बार निष्पादित किया जाएगा, जिसमें प्रिंटिंग भी शामिल है, जो सब कुछ क्रम में कर देगा। यह म्यूटेक्स के लिए भी मामला है। –

+2

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

उत्तर

5

इसे चेहरे पर, synchronized कीवर्ड कार्यात्मक समानstd::mutex लिए है, लेकिन एक नया कीवर्ड और संबद्ध अर्थ विज्ञान (जैसे ब्लॉक सिंक्रनाइज़ क्षेत्र enclosing) यह व्यवहार के लिए इन क्षेत्रों का अनुकूलन करने के लिए बहुत आसान बना देता है शुरू करने से याद।

विशेष रूप से, std::mutex और मित्र सिद्धांतक में कम या ज्यादा अपारदर्शी हैं, जबकि synchronized में स्पष्ट अर्थशास्त्र है। संकलक यह सुनिश्चित नहीं कर सकता कि मानक लाइब्रेरी std::mutex क्या करता है और इसे टीएम का उपयोग करने में कठिन समय लगेगा। std::mutex के मानक लाइब्रेरी कार्यान्वयन को बदलते समय एक सी ++ कंपाइलर को सही तरीके से काम करने की उम्मीद की जाएगी, और इसलिए व्यवहार के बारे में कई धारणाएं नहीं हो सकती हैं।

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

1

ताले सामान्य रूप से अच्छी तरह से लिखते नहीं हैं। पर विचार करें:

// 
// includes and using, omitted to simplify the example 
// 
void move_money_from(Cash amount, BankAccount &a, BankAccount &b) { 
    // 
    // suppose a mutex m within BankAccount, exposed as public 
    // for the sake of simplicity 
    // 
    lock_guard<mutex> lckA { a.m }; 
    lock_guard<mutex> lckB { b.m }; 
    // oversimplified transaction, obviously 
    if (a.withdraw(amount)) 
     b.deposit(amount); 
} 

int main() { 
    BankAccount acc0{/* ... */}; 
    BankAccount acc1{/* ... */}; 
    thread th0 { [&] { 
     // ... 
     move_money_from(Cash{ 10'000 }, acc0, acc1); 
     // ... 
    } }; 
    thread th1 { [&] { 
     // ... 
     move_money_from(Cash{ 5'000 }, acc1, acc0); 
     // ... 
    } }; 
    // ... 
    th0.join(); 
    th1.join(); 
} 

इस मामले में, तथ्य यह है कि th0, acc1 को acc0 से पैसे ले जाकर, acc0.m पहले acc1.m दूसरा लेने की कोशिश कर रहा है, जबकि th1, पैसे ले जाकर acc1 से acc0 करने के लिए, कोशिश कर रहा है acc1.m पहले लेने के लिए, acc0.m दूसरा उन्हें डेडलॉक कर सकता है।

यह उदाहरण oversimplified है, और std::lock() या एक सी ++ 17 variadic lock_guard -equivalent का उपयोग करके हल किया जा सकता है, लेकिन सामान्य मामले जहां एक तीसरे पक्ष के सॉफ्टवेयर का उपयोग कर, नहीं जानने जहां ताले किया जा रहा है है के बारे में सोच लिया या मुक्त किया। वास्तविक जीवन स्थितियों में, ताले के माध्यम से सिंक्रनाइज़ेशन मुश्किल वास्तव में तेज़ हो जाता है।

लेनदेन संबंधी स्मृति सुविधाओं का लक्ष्य सिंक्रनाइज़ेशन प्रदान करना है जो ताले से बेहतर बनाता है; संदर्भ के आधार पर यह एक अनुकूलन सुविधा है, लेकिन यह एक सुरक्षा सुविधा भी है। पुनर्लेखन move_money_from() इस प्रकार है:

void move_money_from(Cash amount, BankAccount &a, BankAccount &b) { 
    synchronized { 
     // oversimplified transaction, obviously 
     if (a.withdraw(amount)) 
     b.deposit(amount); 
    } 
} 

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

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