2012-08-16 12 views
70

मैं सी ++ 11 में thread_local के विवरण से उलझन में हूं। मेरी समझ यह है कि, प्रत्येक थ्रेड में फ़ंक्शन में स्थानीय चर की अनूठी प्रति होती है। वैश्विक/स्थैतिक चर सभी थ्रेडों द्वारा उपयोग किया जा सकता है (संभवतः ताले का उपयोग करके सिंक्रनाइज़ एक्सेस)। और thread_local चर सभी धागे के लिए दृश्यमान हैं, लेकिन केवल उस धागे द्वारा संशोधित किया जा सकता है जिसके लिए उन्हें परिभाषित किया गया है? क्या यह सही है?सी ++ 11 में thread_local का क्या अर्थ है?

उत्तर

73

थ्रेड-लोकल स्टोरेज अवधि एक शब्द है जो प्रतीत होता है कि वैश्विक या स्थैतिक भंडारण अवधि (इसका उपयोग करने वाले कार्यों के दृष्टिकोण से) डेटा को संदर्भित करने के लिए उपयोग किया जाता है, लेकिन वास्तव में, प्रति थ्रेड एक प्रति है।

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

थ्रेड-लोकल में कुछ जो थ्रेड सृजन पर अस्तित्व में लाया गया है और थ्रेड बंद होने पर इसका निपटारा किया जाता है।

कुछ उदाहरणों का पालन करें।

एक यादृच्छिक संख्या जनरेटर के बारे में सोचें जहां बीज प्रति-थ्रेड आधार पर बनाए रखा जाना चाहिए। थ्रेड-स्थानीय बीज का उपयोग करना मतलब है कि प्रत्येक धागे को अन्य धागे से स्वतंत्र, अपने यादृच्छिक संख्या अनुक्रम मिलता है।

यदि आपका बीज यादृच्छिक फ़ंक्शन के भीतर एक स्थानीय चर था, तो इसे हर बार जब आप इसे बुलाया जाता है, तो आपको हर बार एक ही नंबर दिया जाएगा। यदि यह एक वैश्विक था, धागे एक-दूसरे के अनुक्रमों में हस्तक्षेप करेंगे।

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

ये दोनों उदाहरण थ्रेड स्थानीय चर के लिए के भीतर मौजूद फ़ंक्शन का उपयोग करने की अनुमति देते हैं। प्री-थ्रेडेड कोड में, यह फ़ंक्शन के भीतर बस एक स्थिर स्टोरेज अवधि चर होगा। धागे के लिए, यह थ्रेड स्थानीय भंडारण अवधि में संशोधित है।

फिर भी एक और उदाहरण errno जैसा कुछ होगा। आप अपनी कॉल में से किसी एक के विफल होने के बाद errno को संशोधित करने वाले अलग-अलग धागे नहीं चाहते हैं, लेकिन इससे पहले कि आप चर की जांच कर सकें, और फिर भी आप प्रति थ्रेड की प्रतिलिपि चाहते हैं।

This site में विभिन्न संग्रहण अवधि विनिर्देशों का उचित विवरण है।

+5

+1! –

+3

थ्रेड स्थानीय का उपयोग करना 'strtok' के साथ समस्याओं का समाधान नहीं करता है। 'स्ट्रोकोक' भी एक थ्रेडेड वातावरण में टूटा हुआ है। –

+4

क्षमा करें, मुझे इसे दोबारा दोहराएं। यह strtok के साथ किसी भी _new_ समस्याओं को पेश नहीं करता है :-) – paxdiablo

13

थ्रेड-लोकल स्टोरेज स्थिर (= ग्लोबल) स्टोरेज जैसे हर पहलू में है, केवल प्रत्येक थ्रेड में ऑब्जेक्ट की एक अलग प्रति है। ऑब्जेक्ट का जीवन समय या तो थ्रेड स्टार्ट (ग्लोबल वेरिएबल्स के लिए) या पहले प्रारंभिक (ब्लॉक-लोकल स्टेटिक्स के लिए) पर शुरू होता है, और थ्रेड समाप्त होने पर समाप्त होता है (यानी join() कहा जाता है)।

नतीजतन, केवल चर भी घोषित किया जा सकता staticthread_local के रूप में घोषित किया जा सकता है, यानी वैश्विक चर (अधिक सटीक: "नाम स्थान दायरे में" चर), स्थिर वर्ग के सदस्यों, और ब्लॉक स्थैतिक चर (इस स्थिति में static है निहित)।

thread_local Counter c; 

void do_work() 
{ 
    c.increment(); 
    // ... 
} 

int main() 
{ 
    std::thread t(do_work); // your thread-pool would go here 
    t.join(); 
} 

यह धागा उपयोग के आंकड़े, जैसे प्रिंट होगा:

उदाहरण के लिए, यदि आप एक थ्रेड पूल है और पता है कि कैसे अच्छी तरह से अपने काम को लोड संतुलित किया जा रहा था चाहते हैं लगता है इस तरह एक कार्यान्वयन के साथ:

struct Counter 
{ 
    unsigned int c = 0; 
    void increment() { ++c; } 
    ~Counter() 
    { 
     std::cout << "Thread #" << std::this_thread::id() << " was called " 
        << c << " times" << std::endl; 
    } 
}; 
73

आप जब एक चर thread_local तो प्रत्येक धागा अपनी एक प्रतिलिपि है की घोषणा। जब आप इसे नाम से संदर्भित करते हैं, तो वर्तमान धागे से जुड़ी प्रतिलिपि का उपयोग किया जाता है। जैसे

thread_local int i=0; 

void f(int newval){ 
    i=newval; 
} 

void g(){ 
    std::cout<<i; 
} 

void threadfunc(int id){ 
    f(id); 
    ++i; 
    g(); 
} 

int main(){ 
    i=9; 
    std::thread t1(threadfunc,1); 
    std::thread t2(threadfunc,2); 
    std::thread t3(threadfunc,3); 

    t1.join(); 
    t2.join(); 
    t3.join(); 
    std::cout<<i<<std::endl; 
} 

इस कोड होगा उत्पादन "2349", "3249", "4239", "4329", "2439" या "3429", लेकिन बाकी कभी नहीं कुछ भी। प्रत्येक थ्रेड की अपनी प्रति i है, जिसे आवंटित किया जाता है और फिर मुद्रित किया जाता है। main चलने वाला धागा भी अपनी प्रतिलिपि है, जिसे शुरुआत में असाइन किया गया है और फिर अपरिवर्तित छोड़ दिया गया है। ये प्रतियां पूरी तरह से स्वतंत्र हैं, और प्रत्येक का एक अलग पता है।

यह केवल नाम कि संबंध में क्या खास बात है कि --- यदि आप एक thread_local चर का पता ले तो आप सिर्फ एक सामान्य वस्तु है, जो आप स्वतंत्र रूप से धागे के बीच पारित कर सकते हैं करने के लिए एक सामान्य सूचक है। जैसे

thread_local int i=0; 

void thread_func(int*p){ 
    *p=42; 
} 

int main(){ 
    i=9; 
    std::thread t(thread_func,&i); 
    t.join(); 
    std::cout<<i<<std::endl; 
} 

के बाद से i का पता धागा कार्य करने के लिए पारित हो जाता है, तो i मुख्य थ्रेड से संबंधित होने के प्रति भले ही यह thread_local है को सौंपा जा सकता। इस प्रकार इस कार्यक्रम को "42" आउटपुट किया जाएगा। यदि आप ऐसा करते हैं, तो आपको ध्यान रखना होगा कि *p को थ्रेड के बाद एक्सेस नहीं किया गया है, अन्यथा आपको किसी भी अन्य मामले की तरह एक लापरवाही सूचक और अपरिभाषित व्यवहार मिलता है जहां बिंदु-वस्तु ऑब्जेक्ट नष्ट हो जाती है।

thread_local चर "पहले उपयोग से पहले" प्रारंभ किए जाते हैं, इसलिए यदि उन्हें किसी दिए गए धागे से कभी भी छुआ नहीं जाता है तो उन्हें आवश्यक रूप से प्रारंभ नहीं किया जाता है। यह संकलकों को प्रोग्राम में प्रत्येक thread_local चर बनाने के लिए अनुमति देने के लिए है जो पूरी तरह से स्वयं निहित है और उनमें से किसी को भी स्पर्श नहीं करता है। जैसे

struct my_class{ 
    my_class(){ 
     std::cout<<"hello"; 
    } 
    ~my_class(){ 
     std::cout<<"goodbye"; 
    } 
}; 

void f(){ 
    thread_local my_class; 
} 

void do_nothing(){} 

int main(){ 
    std::thread t1(do_nothing); 
    t1.join(); 
} 

इस कार्यक्रम में 2 धागे हैं: मुख्य धागा और मैन्युअल रूप से निर्मित धागा। न तो थ्रेड f पर कॉल करता है, इसलिए thread_local ऑब्जेक्ट का कभी भी उपयोग नहीं किया जाता है। इसलिए यह निर्दिष्ट नहीं किया गया है कि संकलक my_class के 0, 1 या 2 उदाहरण बनाएगा, और आउटपुट हो सकता है, "हैलोहेलोगूडबीगेडबीबी" या "हेल्ग्लूडबी"। `Strtok` सुझाव के लिए

+0

मुझे लगता है कि यह ध्यान रखना महत्वपूर्ण है कि चर की थ्रेड-स्थानीय प्रति चर की एक नई प्रारंभिक प्रति है। यही है, अगर आप 'थ्रेडफनक' की शुरुआत में 'जी()' कॉल जोड़ते हैं, तो आउटपुट '0304029' या जोड़े '02',' 03', और' 04' के कुछ अन्य क्रमपरिवर्तन होगा। यही है, भले ही 9 धागे बनने से पहले 'i' को असाइन किया गया हो, थ्रेड को' i' 'की ताज़ा रूप से निर्मित प्रतिलिपि मिलती है जहां 'i = 0'। यदि 'i' को' thread_local int i = random_integer() 'के साथ असाइन किया गया है, तो प्रत्येक थ्रेड को एक नया यादृच्छिक पूर्णांक प्राप्त होता है। –

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