थ्रेड बनाना महंगा क्यों कहा जाता है?


180

जावा ट्यूटोरियल का कहना है कि थ्रेड बनाना महंगा है। लेकिन वास्तव में यह महंगा क्यों है? वास्तव में क्या हो रहा है जब एक जावा थ्रेड बनाया जाता है जो इसके निर्माण को महंगा बनाता है? मैं बयान को सच मान रहा हूं, लेकिन मुझे बस जेवीएम में थ्रेड निर्माण के यांत्रिकी में दिलचस्पी है।

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

से अभ्यास में जावा संगामिति
तक ब्रायन गोएज़, टिम Peierls, यहोशू बलोच, यूसुफ Bowbeer, डेविड होम्स, डौग ली
प्रिंट ISBN-10: 0-321-34960-1


मैं उस संदर्भ को नहीं जानता, जिसमें आपने जो ट्यूटोरियल पढ़ा है, वे कहते हैं: क्या उनका मतलब यह है कि सृजन स्वयं महंगा है, या यह कि "एक धागा बनाना" महंगा है। जो अंतर मैं दिखाने की कोशिश करता हूं वह थ्रेड बनाने की शुद्ध क्रिया के बीच है (इसे तुरंत या कुछ और कहते हैं), या तथ्य यह है कि आपके पास एक धागा है (इसलिए एक थ्रेड का उपयोग करना: जाहिर है ओवरहेड होना)। कौन सा दावा किया जाता है // आप किसके बारे में पूछना चाहते हैं?
नन

9
@typoknig - एक नया धागा नहीं बनाने की तुलना में महंगा :)
willcodejavaforfood


1
जीत के लिए सूत्र। कार्यों के लिए हमेशा नए धागे बनाने की आवश्यकता नहीं है।
अलेक्जेंडर मिल्स

जवाबों:


149

जावा थ्रेड निर्माण महंगा है क्योंकि इसमें बहुत कम काम शामिल है:

  • थ्रेड स्टैक के लिए स्मृति का एक बड़ा ब्लॉक आवंटित और आरंभीकृत किया जाना है।
  • मेजबान ओएस के साथ मूल धागा बनाने / पंजीकृत करने के लिए सिस्टम कॉल करने की आवश्यकता है।
  • डेस्‍क्रिप्‍टर्स को JVM- आंतरिक डेटा संरचनाओं को बनाना, आरंभ करना और जोड़ना होगा।

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

इन सभी चीजों की लागत प्लेटफ़ॉर्म विशिष्ट है, लेकिन वे मेरे द्वारा कभी भी पार किए गए किसी भी जावा प्लेटफ़ॉर्म पर सस्ते नहीं हैं।


Google खोज ने मुझे एक पुराना बेंचमार्क पाया, जो कि 2002 के विंटेज डुअल प्रोसेसर Xeon पर 2002 के विंटेज लिनक्स पर चलने वाले सन जावा 1.4.1 पर प्रति सेकंड थ्रेड बनाने की दर ~ 4000 था। एक अधिक आधुनिक प्लेटफॉर्म बेहतर संख्या देगा ... और मैं कार्यप्रणाली पर टिप्पणी नहीं कर सकता ... लेकिन कम से कम यह एक बॉलपार्क देता है कि धागा निर्माण कितना महंगा होने की संभावना है।

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


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


मैंने यह देखने के लिए थोड़ी खुदाई की है कि वास्तव में जावा थ्रेड का स्टैक कैसे आवंटित किया जाता है। लिनक्स पर OpenJDK 6 के मामले में, थ्रेड स्टैक को कॉल द्वारा आवंटित किया जाता pthread_createहै जो मूल धागा बनाता है। (JVM pthread_createएक प्रचारित स्टैक पास नहीं करता है ।)

फिर, pthread_createस्टैक के भीतर कॉल को mmapइस प्रकार आवंटित किया जाता है:

mmap(0, attr.__stacksize, 
     PROT_READ|PROT_WRITE|PROT_EXEC, 
     MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)

के अनुसार man mmap, MAP_ANONYMOUSध्वज स्मृति को शून्य से आरंभ करने का कारण बनता है।

इस प्रकार, भले ही यह आवश्यक न हो कि नए जावा थ्रेड स्टैक शून्य हैं (JVM कल्पना के अनुसार), व्यवहार में (कम से कम लिनक्स पर OpenJDK 6 के साथ) वे शून्य हैं।


2
@ रैडवल्ड - यह शुरुआती हिस्सा है जो महंगा है। कहीं न कहीं, कुछ (जैसे जीसी, या ओएस) ब्लॉक को धागे के ढेर में बदलने से पहले बाइट्स को शून्य कर देगा। यह विशिष्ट हार्डवेयर पर भौतिक मेमोरी चक्र लेता है।
स्टीफन C

2
"कहीं, कुछ (जैसे जीसी, या ओएस) बाइट्स को शून्य कर देगा"। यह? यदि सुरक्षा कारणों से ओएस को नए मेमोरी पेज के आवंटन की आवश्यकता होती है, तो लेकिन वह अपूर्व होगा। और ओएस पहले से ही शून्य-एड पृष्ठों (आईआईआरसी, लिनक्स ऐसा करता है) का कैश रख सकता है। जीसी परेशान क्यों होगा, यह देखते हुए कि जेवीएम किसी भी जावा प्रोग्राम को अपनी सामग्री को पढ़ने से रोकेगा? ध्यान दें कि मानक C malloc()फ़ंक्शन, जिसे JVM अच्छी तरह से उपयोग कर सकता है, यह गारंटी नहीं देता है कि आवंटित मेमोरी शून्य-एड (संभवतः ऐसी प्रदर्शन समस्याओं से बचने के लिए) है।
Raedwald

1
stackoverflow.com/questions/2117072/… निष्कर्ष है कि "एक प्रमुख कारक प्रत्येक थ्रेड के लिए आवंटित स्टैक मेमोरी है"।
Raedwald

2
@Raedwald - स्टैक वास्तव में कैसे आवंटित किया गया है, इसकी जानकारी के लिए अद्यतन उत्तर देखें।
स्टीफन C

2
यह संभव है (संभावित रूप से भी) कि mmap()कॉल द्वारा आवंटित मेमोरी पेज कॉपी-ऑन-राइट मैप पर एक शून्य पेज पर हैं, इसलिए उनका initailisation mmap()स्वयं के भीतर नहीं होता है, लेकिन जब पेज पहले लिखे जाते हैं, और तब केवल एक पेज पर लिखा जाता है एक वक़्त। यही है, जब थ्रेड निष्पादन शुरू होता है, निर्माता धागे के बजाय निर्मित धागे द्वारा लागत बॉर्न के साथ।
रायडवल

76

दूसरों ने चर्चा की है कि थ्रेडिंग की लागत कहाँ से आती है। यह उत्तर कवर करता है कि थ्रेड बनाना क्यों कई ऑपरेशनों की तुलना में महंगा नहीं है, लेकिन कार्य निष्पादन विकल्पों की तुलना में अपेक्षाकृत महंगा है, जो अपेक्षाकृत कम खर्चीला है।

किसी कार्य को किसी अन्य थ्रेड में चलाने का सबसे स्पष्ट विकल्प कार्य को उसी थ्रेड में चलाना है। यह मानने वालों के लिए समझ पाना मुश्किल है कि अधिक धागे हमेशा बेहतर होते हैं। तर्क यह है कि यदि कार्य को किसी अन्य थ्रेड में जोड़ने का ओवरहेड आपके द्वारा सहेजे जाने वाले समय से अधिक है, तो कार्य को वर्तमान थ्रेड में निष्पादित करने के लिए तेज़ हो सकता है।

एक अन्य विकल्प थ्रेड पूल का उपयोग करना है। एक थ्रेड पूल दो कारणों से अधिक कुशल हो सकता है। 1) यह पहले से निर्मित थ्रेड्स का पुन: उपयोग करता है। 2) आप अधिकतम प्रदर्शन सुनिश्चित करने के लिए थ्रेड्स की संख्या को ट्यून / नियंत्रित कर सकते हैं।

निम्नलिखित कार्यक्रम प्रिंट…।

Time for a task to complete in a new Thread 71.3 us
Time for a task to complete in a thread pool 0.39 us
Time for a task to complete in the same thread 0.08 us
Time for a task to complete in a new Thread 65.4 us
Time for a task to complete in a thread pool 0.37 us
Time for a task to complete in the same thread 0.08 us
Time for a task to complete in a new Thread 61.4 us
Time for a task to complete in a thread pool 0.38 us
Time for a task to complete in the same thread 0.08 us

यह एक तुच्छ कार्य के लिए एक परीक्षण है जो प्रत्येक थ्रेडिंग विकल्प के ओवरहेड को उजागर करता है। (यह परीक्षण कार्य उस प्रकार का कार्य है जो वास्तव में वर्तमान थ्रेड में सबसे अच्छा किया जाता है।)

final BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
Runnable task = new Runnable() {
    @Override
    public void run() {
        queue.add(1);
    }
};

for (int t = 0; t < 3; t++) {
    {
        long start = System.nanoTime();
        int runs = 20000;
        for (int i = 0; i < runs; i++)
            new Thread(task).start();
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in a new Thread %.1f us%n", time / runs / 1000.0);
    }
    {
        int threads = Runtime.getRuntime().availableProcessors();
        ExecutorService es = Executors.newFixedThreadPool(threads);
        long start = System.nanoTime();
        int runs = 200000;
        for (int i = 0; i < runs; i++)
            es.execute(task);
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in a thread pool %.2f us%n", time / runs / 1000.0);
        es.shutdown();
    }
    {
        long start = System.nanoTime();
        int runs = 200000;
        for (int i = 0; i < runs; i++)
            task.run();
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in the same thread %.2f us%n", time / runs / 1000.0);
    }
}
}

जैसा कि आप देख सकते हैं, एक नया धागा बनाने में केवल ~ 70 µ का खर्च आता है। यह कई में तुच्छ माना जा सकता है, यदि अधिकांश नहीं, तो मामलों का उपयोग करें। अपेक्षाकृत बोलना विकल्प से अधिक महंगा है और कुछ स्थितियों के लिए थ्रेड पूल या थ्रेड्स का उपयोग नहीं करना एक बेहतर उपाय है।


8
यह वहां कोड का एक बड़ा टुकड़ा है। संक्षिप्त, टू द पॉइंट और स्पष्ट रूप से अपने जिस्ट को प्रदर्शित करता है।
निकोलस

अंतिम ब्लॉक में, मेरा मानना ​​है कि परिणाम तिरछा है, क्योंकि पहले दो ब्लॉकों में समानांतर धागा समानांतर में निकाल रहा है जैसा कि कार्यकर्ता धागे डाल रहे हैं। हालांकि अंतिम ब्लॉक में, लेने की क्रिया सभी को क्रमिक रूप से किया जाता है, इसलिए यह मूल्य को कम कर रहा है। आप शायद थ्रेड का उपयोग करने के लिए प्रतीक्षा करने के लिए queue.clear () का उपयोग कर सकते हैं और एक CountDownLatch का उपयोग कर सकते हैं।
विक्टर ग्राज़ी

@VictorGrazi मैं मान रहा हूं कि आप परिणाम को केंद्र में रखना चाहते हैं। यह प्रत्येक मामले में समान मात्रा में काम कर रहा है। एक गिनती नीचे की कुंडी थोड़ी तेज होगी।
पीटर लॉरी

वास्तव में, क्यों नहीं यह लगातार कुछ तेजी से करते हैं, जैसे एक काउंटर बढ़ाना; पूरी BlockingQueue चीज़ ड्रॉप करें। संकलक ऑपरेशन को अनुकूलित करने से कंपाइलर को रोकने के लिए अंत में काउंटर की जाँच करें
विक्टर ग्राज़ी

@grazi आप इस मामले में ऐसा कर सकते हैं लेकिन आप अधिकांश यथार्थवादी मामलों में नहीं करेंगे क्योंकि काउंटर पर प्रतीक्षा करना अक्षम हो सकता है। यदि आपने ऐसा किया है कि उदाहरणों के बीच का अंतर और भी अधिक होगा।
पीटर लॉरी

31

सिद्धांत रूप में, यह जेवीएम पर निर्भर करता है। व्यवहार में, प्रत्येक थ्रेड में स्टैक मेमोरी की अपेक्षाकृत बड़ी मात्रा होती है (प्रति डिफ़ॉल्ट 256 KB, मुझे लगता है)। इसके अतिरिक्त, थ्रेड्स को OS थ्रेड्स के रूप में लागू किया जाता है, इसलिए उन्हें बनाने से एक OS कॉल, यानी एक संदर्भ स्विच शामिल होता है।

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


9
Btw केबी = किलो-बिट, केबी = किलो बाइट। जीबी = गीगा बिट, जीबी = गीगा बाइट।
पीटर लॉरी

@PeterLawrey क्या हम 'k' को 'kb' और 'kB' में कैपिटल करते हैं, इसलिए 'Gb' और 'GB' में समरूपता है? ये चीजें मुझे खराब करती हैं।
जैक

3
@ जैक एक K= 1024 और k= 1000 है;) en.wikipedia.org/wiki/Kibibyte
पीटर लॉरी

9

धागे दो प्रकार के होते हैं:

  1. उचित सूत्र : ये अंतर्निहित ऑपरेटिंग सिस्टम की थ्रेडिंग सुविधाओं के आसपास के अमूर्त हैं। इसलिए थ्रेड क्रिएशन, सिस्टम जितना महंगा है - हमेशा ओवरहेड होता है।

  2. "ग्रीन" धागे : जेवीएम द्वारा निर्मित और निर्धारित किए गए, ये सस्ते हैं, लेकिन कोई उचित समानता नहीं होती है। ये धागे की तरह व्यवहार करते हैं, लेकिन ओएस में जेवीएम थ्रेड के भीतर निष्पादित होते हैं। वे अक्सर मेरे ज्ञान के लिए उपयोग नहीं किए जाते हैं।

सबसे बड़ा कारक जो मैं थ्रेड-सृजन ओवरहेड में सोच सकता हूं, वह स्टैक-आकार है जिसे आपने अपने थ्रेड्स के लिए परिभाषित किया है। VM को चलाते समय थ्रेड स्टैक-आकार को एक पैरामीटर के रूप में पारित किया जा सकता है।

इसके अलावा, धागा निर्माण ज्यादातर ओएस-निर्भर है, और यहां तक ​​कि वीएम-कार्यान्वयन-निर्भर भी।

अब, मुझे कुछ बताने की आवश्यकता है: यदि आप प्रति सेकंड 2000 रनिंग फायरिंग की योजना बना रहे हैं, तो आपके थ्रेड के हर सेकंड में थ्रेड बनाना महंगा है। JVM को संभालने के लिए डिज़ाइन नहीं किया गया है । यदि आपके पास स्थिर श्रमिकों के एक जोड़े हैं जिन्हें निकाल दिया जाएगा और अधिक से अधिक नहीं मारा जाएगा, तो आराम करें।


19
"... स्थिर श्रमिकों के एक जोड़े को निकाल दिया जाएगा और मार डाला जाएगा ..." मैंने कार्यस्थल की स्थितियों के बारे में क्यों सोचना शुरू किया? :-)
स्टीफन सी

6

बनाने के Threadsलिए एक उचित मात्रा में मेमोरी आवंटित करने की आवश्यकता होती है क्योंकि इसमें एक नहीं, बल्कि दो नए स्टैक (एक जावा कोड के लिए, एक नेटिव कोड के लिए होता है)। उपयोग की निष्पादकों / थ्रेड ताल के लिए कई कार्य के लिए धागे पुन: उपयोग से, भूमि के ऊपर से बच सकते हैं निर्वाहक


@Redwald, jvm क्या है जो अलग-अलग स्टैक का उपयोग करता है?
बेस्ट

1
फिलिप जेपी कहते हैं 2 स्टैक।
राडवल्ड

जहां तक ​​मुझे पता है, सभी JVM प्रति थ्रेड दो स्टैक आवंटित करते हैं। यह कचरा संग्रह के लिए जावा कोड (यहां तक ​​कि जब JITed) से मुक्त कास्टिंग सी से अलग व्यवहार करने के लिए सहायक है।
फिलिप जेएफ

@Philip JF क्या आप विस्तृत कर सकते हैं? जावा कोड के लिए 2 और मूल कोड के लिए एक का मतलब क्या है? यह क्या करता है?
गुरिंदर

"जहां तक ​​मुझे पता है, सभी जेवीएम प्रति थ्रेड दो स्टैक आवंटित करते हैं।" - मैंने कभी ऐसा कोई सबूत नहीं देखा जो इसका समर्थन करता हो। शायद आप जेवीएम कल्पना में ऑपस्टैक के वास्तविक स्वरूप को गलत समझ रहे हैं। (यह बाइटकोड के व्यवहार को मॉडलिंग करने का एक तरीका है, न कि कुछ ऐसा जो उन्हें चलाने के लिए रनटाइम में इस्तेमाल करने की आवश्यकता है।)
स्टीफन C

1

जाहिर है कि सवाल का क्रेज 'महंगा' है।

एक थ्रेड को स्टैक बनाने और रन विधि के आधार पर स्टैक को इनिशियलाइज़ करने की आवश्यकता होती है।

इसे नियंत्रण स्थिति संरचनाओं को स्थापित करने की आवश्यकता है, अर्थात, यह किस राज्य में चलने योग्य, प्रतीक्षा आदि में है।

इन चीजों को स्थापित करने के आसपास तुल्यकालन का एक अच्छा सौदा है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.