मल्टीथ्रेडिंग: क्या मैं इसे गलत कर रहा हूं?


23

मैं एक ऐसे एप्लिकेशन पर काम कर रहा हूं जो संगीत बजाता है।

प्लेबैक के दौरान, अक्सर चीजों को अलग-अलग थ्रेड्स पर होने की आवश्यकता होती है क्योंकि उन्हें एक साथ होने की आवश्यकता होती है। उदाहरण के लिए, एक राग के नोटों को एक साथ सुनने की आवश्यकता होती है, इसलिए प्रत्येक को अपना स्वयं का थ्रेड खेलने के लिए सौंपा जाता है (इसे स्पष्ट करने के लिए संपादित करें: note.play()थ्रेड को कॉल करना तब तक फ्रीज होता है जब तक कि नोट बजाया नहीं जाता , और यही कारण है कि मुझे तीन की आवश्यकता है एक ही समय में तीन नोटों को सुनने के लिए अलग धागे।)

इस तरह का व्यवहार संगीत के एक टुकड़े के प्लेबैक के दौरान कई धागे बनाता है।

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

तो एक प्रगति खेलने के लिए छद्म कोड इस तरह दिखता है:

void playProgression(Progression prog){
    for(Chord chord : prog)
        for(Note note : chord)
            runOnNewThread( func(){ note.play(); } );
}

तो यह मानते हुए कि प्रगति के 4 तार हैं, और हम इसे दो बार खेलते हैं, जैसे कि हम 3 notes * 4 chords * 2 times= 24 धागे खोल रहे हैं । और यह सिर्फ एक बार इसे खेलने के लिए है।

दरअसल, यह व्यवहार में ठीक काम करता है। मैं किसी भी ध्यान देने योग्य विलंबता, या इसके परिणामस्वरूप बग को नोटिस नहीं करता हूं।

लेकिन मैं पूछना चाहता था कि क्या यह सही अभ्यास है, या अगर मैं कुछ गलत कर रहा हूं। क्या उपयोगकर्ता द्वारा किसी बटन को धक्का देने पर हर बार इतने सारे धागे बनाना उचित है? यदि नहीं, तो मैं इसे अलग तरीके से कैसे कर सकता हूं?


14
शायद आपको इसके बजाय ऑडियो को मिलाने पर विचार करना चाहिए? मुझे नहीं पता कि आप किस फ्रेमवर्क का उपयोग कर रहे हैं, लेकिन यहां एक उदाहरण है: wiki.libsdl.org/SDL_MixAudioFormat या, आप चैनलों का उपयोग कर सकते हैं: libsdl.org/projects/SDL_mixer/docs/SDL_mixer_25.html#SEC25
Rufflewind

5
Is it reasonable to create so many threads...भाषा के थ्रेडिंग मॉडल पर निर्भर करता है। समानता के लिए उपयोग किए जाने वाले थ्रेड्स को अक्सर ओएस स्तर पर नियंत्रित किया जाता है ताकि ओएस उन्हें कई कोर में मैप कर सके। इस तरह के धागे बनाने और बीच में स्विच करने के लिए महंगे हैं। कंसीडर के लिए थ्रेड्स (दो कार्यों को एक साथ करना, जरूरी नहीं कि दोनों को एक साथ निष्पादित करना) को भाषा / वीएम स्तर पर कार्यान्वित किया जा सकता है और इसे 10 नेटवर्क सॉकेट पर अधिक या कम से बात करने के लिए उत्पादन करने और स्विच करने के लिए बेहद "सस्ता" बनाया जा सकता है। एक साथ, लेकिन जरूरी नहीं कि आपको अधिक सीपीयू थ्रूपुट मिल जाए।
डोभाल

3
एक तरफ, दूसरे निश्चित रूप से थ्रेड्स के बारे में सही हैं कि एक ही बार में कई ध्वनियों को चलाने का गलत तरीका है।
डोभाल

3
क्या आपके पास ध्वनि तरंगों के काम करने के बारे में अधिक जानकारी है? आमतौर पर आप दो साउंड वेव्स (एक ही बिटरेट पर निर्दिष्ट) के मूल्यों को एक साथ एक नए साउंड वेव में कंपोज़ करके कॉर्ड बनाते हैं। जटिल तरंगों को सरल लोगों से बनाया जा सकता है; आपको केवल एक गाना बजाने के लिए एक एकल तरंग की आवश्यकता होती है।
KChaloux

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

जवाबों:


46

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

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


36
या इसे अलग तरह से बताने के लिए - सिस्टम एक बार शुरू होने के बाद किसी भी थ्रेड के आदेश, अनुक्रम या अवधि की गारंटी नहीं देगा और न ही कर सकता है।
जेम्स एंडरसन

2
@JamesAnderson सिवाय अगर कोई सिंक्रनाइज़ेशन में बड़े पैमाने पर प्रयास करेगा, जो अंत में फिर से लगभग स्क्वीटली होगा।
मार्क

'एपीआई' से आपका मतलब है कि मैं जिस ऑडियो लाइब्रेरी का उपयोग कर रहा हूं?
अवीव कोहन

1
@ प्रोग हाँ। मुझे पूरा यकीन है कि यह कुछ और सुविधाजनक है कि note.play ()
ptyx

26

लॉकस्टेप में निष्पादित थ्रेड्स पर भरोसा न करें। प्रत्येक ऑपरेटिंग सिस्टम जो मुझे पता है कि गारंटी नहीं देता है कि थ्रेड एक दूसरे के अनुरूप समय में निष्पादित होते हैं। इसका मतलब यह है कि यदि सीपीयू 10 थ्रेड चलाता है, तो वे जरूरी नहीं कि किसी दिए गए सेकंड में समान समय प्राप्त करें। वे जल्दी से सिंक से बाहर निकल सकते थे, या वे पूरी तरह से सिंक में रह सकते थे। यह सूत्र की प्रकृति है: कुछ भी गारंटी नहीं है, क्योंकि उनके निष्पादन का व्यवहार गैर-नियतात्मक है

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

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

संबंधित पढ़ना: निर्माता-उपभोक्ता समस्या


1
जवाब के लिए धन्यवाद। मुझे 100% यकीन नहीं है कि मैं समझता हूं कि आप जवाब दे रहे हैं, लेकिन मैं यह सुनिश्चित करना चाहता था कि आप यह समझें कि मुझे एक ही समय में 3 नोट खेलने के लिए 3 थ्रेड्स की आवश्यकता क्यों है: यह इसलिए है क्योंकि कॉल note.play()थ्रेड को जमा करता है जब तक कि नोट खेल नहीं हो जाता। तो मेरे लिए play()एक ही समय में 3 नोट करने में सक्षम होने के लिए , मुझे ऐसा करने के लिए 3 अलग-अलग थ्रेड्स की आवश्यकता है। क्या आपका समाधान मेरी स्थिति को संबोधित करता है?
अवीव कोहन

नहीं, और यह सवाल से स्पष्ट नहीं था। क्या एक राग को बजाने के लिए कोई धागा नहीं है?

4

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

void playProgression(Progression prog){
    for(Chord chord : prog)
        for(Note note : chord)
            otherthread.startPlaying(note);
}

(नोट को केवल अतुल्यकालिक रूप से शुरू करने और इसके खत्म होने की प्रतीक्षा किए बिना चल रहा है) की अवधारणा पर ध्यान दें

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

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


1
ओपी का कहना है कि एपीआई एक बार में केवल एक ही नोट खेल सकता है
मूइंग डक

4
@MooDDuck अगर API मिक्स कर सकता है, तो इसे मिलाना चाहिए; यदि ओपी कहता है कि एपीआई मिश्रण नहीं कर सकता है, तो समाधान आपके कोड में मिलाना है और यह है कि अन्य धागा एप के माध्यम से my_mixed_notes_from_whole_chord_progression.play () प्रदर्शन करते हैं।
पीटरिस

1

अच्छा, हाँ, आप कुछ गलत कर रहे हैं।

पहली बात यह है कि थ्रेड बनाना महंगा है। यह केवल एक फ़ंक्शन को कॉल करने की तुलना में बहुत अधिक ओवरहेड है।

तो आपको क्या करना चाहिए, यदि आपको इस कार्य के लिए कई थ्रेड्स की आवश्यकता है, तो थ्रेड को रीसायकल करना है।

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

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

आपको एक नज़र रखना चाहिए कि क्या एक धागे पर कई नोट चलाना संभव नहीं है, जिससे कि धागा सभी नोटों को तैयार करता है और फिर केवल "स्टार्ट" कमांड देता है।

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


"[क्या यह संभव है] एक धागे पर कई नोटों को चलाने के लिए, ताकि धागा सभी नोटों को तैयार करे और उसके बाद केवल" स्टार्ट "कमांड दे।" - यह मेरा पहला विचार भी था। मुझे कभी-कभी आश्चर्य होता है कि क्या मल्टीथ्रेडिंग (जैसे प्रोग्रामर.स्टैकएक्सचेंज .com/questions/43321/… ) के बारे में टिप्पणी के साथ बमबारी की जा रही है, डिजाइन के दौरान बहुत सारे प्रोग्रामर भटक नहीं सकते हैं। मैं किसी भी बड़ी, अप-फ्रंट प्रक्रिया पर संदेह कर रहा हूं जो कि धागे के एक गुच्छा की आवश्यकता को समाप्त करता है। मैं एक एकल धागा समाधान के लिए स्पष्ट रूप से देखने की सलाह दूंगा।
user1172763

1

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

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

इस डिजाइन का एक फायदा यह है कि थ्रेड्स की संख्या एक साथ नोट + 1 (टाइमिंग लूप) की अधिकतम संख्या से अधिक नहीं होगी। साथ ही टाइमिंग लूप आपको टाइमिंग में फिसलन से बचाता है जो थ्रेड लेटेंसी के कारण हो सकता है। तीसरे रूप में राग का टेंपो तय नहीं होता है और समय की गणना में बदलाव करके इसे बदला जा सकता है।

एक तरफ के रूप में, मैं अन्य टिप्पणीकारों से सहमत हूं कि कार्य करने के लिए फ़ंक्शन note.play()बहुत खराब एपीआई है। कोई भी उचित साउंड एपीआई आपको अधिक लचीले तरीके से नोट्स मिलाने और शेड्यूल करने की अनुमति देगा। उस ने कहा, कभी-कभी हमें जीना है जो हमें मिला है :)


0

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

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

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

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