C ++ 11 में थ्रेड_लोक का क्या अर्थ है?


131

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

जवाबों:


151

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

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

थ्रेड-लोकल कुछ ऐसा है जो थ्रेड निर्माण पर अस्तित्व में लाया जाता है और थ्रेड के रुकने पर उसका निपटान किया जाता है।

कुछ उदाहरण अनुसरण करते हैं।

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

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

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

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

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

इस साइट में अलग-अलग स्टोरेज अवधि निर्दिष्ट करने वालों का एक उचित विवरण है।


4
थ्रेड लोकल का उपयोग करने से समस्याओं का समाधान नहीं होता है strtokstrtokएक भी पिरोया वातावरण में भी टूट गया है।
जेम्स कंज

11
क्षमा करें, मुझे लगता है कि rephrase। यह strtok :-)
paxdiablo

7
दरअसल, r"री-एंट्रेंट" के लिए खड़ा है, जिसका थ्रेड सुरक्षा से कोई लेना-देना नहीं है। यह सही है कि आप थ्रेड-लोकल स्टोरेज के साथ थ्रेड-सेफ़ को सुरक्षित रूप से काम कर सकते हैं, लेकिन आप उन्हें दोबारा नहीं बना सकते।
केरेक एसबी

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

3
यह गड़बड़ करेगा:while (something) { char *next = strtok(whatever); someFunction(next); // someFunction calls strtok }
'20

135

जब आप एक चर घोषित करते हैं 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 unused;
}

void do_nothing(){}

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

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


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

ऐसा नहीं है की एक क्रमचय 02, 03, 04, वहाँ की तरह अन्य दृश्यों हो सकता है020043
Hongxu चेन

दिलचस्प टिडबिट मैं अभी पाया: जीसीसी टेम्पलेट तर्क के रूप में एक थ्रेड_लोकल वैरिएबल के पते का उपयोग करने का समर्थन करता है, लेकिन अन्य कंपाइलर (इस लेखन के रूप में; मुझे यकीन नहीं है कि मानक के बारे में क्या कहना है, या यदि यह एक अनिर्दिष्ट क्षेत्र है।
jwd

23

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

नतीजतन, केवल वे चर भी घोषित staticकिए जा सकते हैं जिन्हें thread_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;
     }
};
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.