มัลติเธรด: ฉันทำผิดหรือเปล่า?


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...ขึ้นอยู่กับรูปแบบเกลียวของภาษา เธรดที่ใช้สำหรับการขนานจะได้รับการจัดการในระดับระบบปฏิบัติการเพื่อให้ OS สามารถแมปไปยังหลายคอร์ได้ เธรดดังกล่าวมีราคาแพงในการสร้างและสลับระหว่าง หัวข้อสำหรับการทำงานพร้อมกัน (interleaving สองงานไม่จำเป็นต้องดำเนินการทั้งสองพร้อมกัน) สามารถนำมาใช้ในระดับภาษา / VM และสามารถทำให้ "ถูก" มากในการผลิตและสลับระหว่างดังนั้นคุณสามารถพูดคุยกับ 10 เครือข่ายซ็อกเก็ตมากหรือน้อย พร้อมกัน แต่คุณไม่จำเป็นต้องได้รับปริมาณงานของ CPU มากขึ้น
Doval

3
นอกเหนือจากนั้นคนอื่น ๆ มีความถูกต้องแน่นอนเกี่ยวกับหัวข้อเป็นวิธีที่ผิดในการจัดการกับการเล่นหลายเสียงในครั้งเดียว
Doval

3
คุณมีความคุ้นเคยกับคลื่นเสียงมากแค่ไหน? โดยทั่วไปคุณสร้างเสียงประสานโดยการเขียนค่าของคลื่นเสียงสองอัน (ระบุที่บิตเรตเดียวกัน) เข้าด้วยกันเป็นคลื่นเสียงใหม่ คลื่นที่ซับซ้อนสามารถสร้างขึ้นจากคลื่นที่เรียบง่าย คุณจำเป็นต้องใช้รูปแบบของคลื่นเดียวในการเล่นเพลง
KChaloux

เนื่องจากคุณบอกว่า note.play () ไม่ใช่แบบอะซิงโครนัสดังนั้นเธรดสำหรับแต่ละ note.play () จึงเป็นวิธีการเล่นโน้ตหลาย ๆ ตัวพร้อมกัน UNLESS .. คุณสามารถรวมบันทึกเหล่านั้นเข้ากับโน้ตที่คุณเล่นบนเธรดเดี่ยว ถ้าเป็นไปไม่ได้แล้วด้วยแนวทางของคุณคุณจะต้องใช้กลไกบางอย่างเพื่อให้แน่ใจว่าพวกเขายังคงซิงค์อยู่
pnizzle

คำตอบ:


46

สมมติฐานหนึ่งที่คุณกำลังทำอาจไม่ถูกต้อง: คุณต้องการ (เหนือสิ่งอื่นใด) ที่เธรดของคุณจะทำงานพร้อมกัน อาจใช้งานได้ 3 ครั้ง แต่ในบางจุดระบบจะต้องจัดลำดับความสำคัญว่าเธรดใดที่จะรันก่อนและหนึ่งอันรอ

การติดตั้งใช้งานของคุณจะขึ้นอยู่กับ API ของคุณในที่สุด แต่ API ที่ทันสมัยส่วนใหญ่จะแจ้งให้คุณทราบล่วงหน้าว่าคุณต้องการเล่นอะไรและดูแลเวลาและเข้าคิวด้วยตนเอง หากคุณต้องเขียนโค้ด API ด้วยตัวเองให้เพิกเฉยต่อ API ของระบบที่มีอยู่ (ทำไมคุณจะต้อง!) คิวเหตุการณ์ที่ผสมโน้ตของคุณและเล่นโน้ตจากเธรดเดี่ยวดูเหมือนว่าวิธีการที่ดีกว่าเธรดต่อรุ่นโน้ต


36
หรือระบุว่าแตกต่าง - ระบบจะไม่และไม่สามารถรับประกันการสั่งซื้อลำดับหรือระยะเวลาของเธรดใด ๆ ที่เริ่มต้น
James Anderson

2
@JamesAnderson ยกเว้นว่าจะมีความพยายามอย่างมากในการซิงโครไนซ์ซึ่งจะจบลงที่เกือบจะกลายเป็นเรื่องใหญ่โตในที่สุดอีกครั้ง
ทำเครื่องหมาย

โดย 'API' คุณหมายถึงไลบรารีเสียงที่ฉันใช้หรือไม่
Aviv Cohn

1
@Prog ใช่ ฉันค่อนข้างมั่นใจว่ามันมีบางอย่างที่สะดวกกว่า note.play ()
ptyx

26

อย่าพึ่งพาเธรดที่กำลังดำเนินการใน lockstep ทุกระบบปฏิบัติการที่ฉันรู้จักไม่ได้รับประกันว่าเธรดจะทำงานในเวลาที่สอดคล้องกัน ซึ่งหมายความว่าหาก CPU ทำงาน 10 เธรดจะไม่ได้รับเวลาเท่ากันในวินาทีที่กำหนด พวกเขาสามารถออกจากซิงค์ได้อย่างรวดเร็วหรือพวกเขาสามารถอยู่ในซิงค์อย่างสมบูรณ์แบบ นั่นคือลักษณะของเธรด: ไม่มีสิ่งใดรับประกันได้เนื่องจากพฤติกรรมของการดำเนินการของพวกเขานั้นไม่ได้กำหนดขึ้น

สำหรับแอปพลิเคชันเฉพาะนี้ฉันเชื่อว่าคุณต้องมีเธรดเดียวที่ใช้งานโน้ตดนตรี ก่อนที่มันจะสามารถใช้บันทึกบางความต้องการของกระบวนการอื่น ๆ ที่จะผสานตราสารพนักงานสิ่งที่เป็นหนึ่งชิ้นส่วนของเพลง

ให้เราสมมติว่าคุณมีเช่นสามกระทู้ผลิตโน้ต ฉันจะซิงโครไนซ์พวกเขาในโครงสร้างข้อมูลเดียวที่พวกเขาสามารถใส่เพลงของพวกเขา อีกเธรด (สำหรับผู้บริโภค) อ่านโน้ตที่รวมกันแล้วเล่น อาจต้องมีการหน่วงเวลาสั้น ๆ ที่นี่เพื่อให้แน่ใจว่าการกำหนดเวลาของเธรดไม่ทำให้โน้ตหายไป

การอ่านที่เกี่ยวข้อง: ปัญหาผู้ผลิตผู้บริโภค


1
ขอบคุณสำหรับคำตอบ. ฉันไม่แน่ใจ 100% ว่าฉันเข้าใจคุณตอบ แต่ฉันต้องการให้แน่ใจว่าคุณเข้าใจว่าทำไมฉันต้องมี 3 เธรดเพื่อเล่น 3 โน้ตในเวลาเดียวกัน: เพราะการโทรnote.play()ค้างเธรดจนกว่าโน้ตจะเล่นเสร็จ ดังนั้นเพื่อให้ฉันสามารถplay()บันทึกได้ 3 รายการในเวลาเดียวกันฉันต้องการ 3 เธรดที่แตกต่างกันเพื่อทำสิ่งนี้ โซลูชันของคุณตอบสนองสถานการณ์ของฉันหรือไม่
Aviv Cohn

ไม่และนั่นไม่ชัดเจนจากคำถาม มีวิธีใดไหมที่เธรดจะเล่นคอร์ด?

4

แนวทางแบบคลาสสิกสำหรับปัญหาเพลงนี้คือการใช้สองเธรด หนึ่งเธรดที่มีลำดับความสำคัญต่ำกว่าจะจัดการกับ UI หรือรหัสการสร้างเสียงเช่น

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

(สังเกตแนวคิดของการเริ่มบันทึกย่อแบบอะซิงโครนัสและดำเนินต่อไปโดยไม่รอให้จบ)

และครั้งที่สองด้ายเรียลไทม์อย่างต่อเนื่องจะดูทั้งหมดบันทึกเสียงตัวอย่าง ฯลฯ ที่ควรจะเล่นตอนนี้ ผสมพวกเขาและส่งออกรูปคลื่นสุดท้าย ส่วนนี้อาจ (และบ่อยครั้ง) นำมาจากห้องสมุดบุคคลที่สาม

เธรดนั้นมักจะมีความอ่อนไหวมากต่อ "ความอดอยาก" ของทรัพยากร - หากการประมวลผลเอาต์พุตเสียงใด ๆ ถูกปิดกั้นนานกว่าเอาต์พุตของบัฟเฟอร์การ์ดเสียงของคุณแล้วมันจะทำให้สิ่งประดิษฐ์ที่ได้ยินเช่นการขัดจังหวะหรือปรากฏ หากคุณมี 24 เธรดที่ส่งสัญญาณเสียงโดยตรงคุณมีโอกาสสูงกว่าที่หนึ่งในนั้นจะพูดติดอ่างในบางจุด เรื่องนี้มักจะถือว่าเป็นที่ยอมรับไม่ได้เพราะมนุษย์ค่อนข้างไวต่อเสียงบกพร่อง (มากกว่าภาพวัตถุ) และสังเกตเห็นแม้แต่การพูดติดอ่างเล็กน้อย


1
OP กล่าวว่า API เท่านั้นสามารถเล่นหนึ่งทราบในเวลา
มอเป็ด

4
@MooingDuck ถ้า API สามารถผสมได้ก็ควรผสม; ถ้า OP บอกว่า API ไม่สามารถผสมกันได้วิธีแก้ไขคือให้ผสมในรหัสของคุณและให้เธรดอื่นดำเนินการ my_mixed_notes_from_whole_chord_progression.play () ผ่าน api
Peteris

1

ใช่คุณกำลังทำอะไรผิด

สิ่งแรกคือการสร้างเธรดมีราคาแพง มันมีค่าใช้จ่ายมากกว่าการเรียกฟังก์ชั่น

ดังนั้นสิ่งที่คุณควรทำถ้าคุณต้องการหลายเธรดสำหรับงานนี้คือการรีไซเคิลเธรด

สำหรับฉันแล้วคุณมีเธรดหลักหนึ่งเธรดที่จัดตารางเวลาของเธรดอื่น ดังนั้นเธรดหลักจะนำเพลงและเริ่มเธรดใหม่เมื่อใดก็ตามที่มีโน้ตใหม่ที่จะเล่น ดังนั้นแทนที่จะปล่อยให้เธรดตายและเริ่มต้นใหม่ให้เธรดอื่น ๆ ยังมีชีวิตอยู่ใน sleep วนที่ดีกว่าซึ่งจะตรวจสอบทุกๆ x milliseconds (หรือ nanoseconds) ทุกครั้งถ้ามีโน้ตใหม่ที่จะเล่นและนอนหลับ เธรดหลักจะไม่เริ่มเธรดใหม่ แต่บอกเธรดพูลที่มีอยู่เพื่อเล่นโน้ต เฉพาะในกรณีที่มีเธรดไม่เพียงพอในพูลโปรแกรมสามารถสร้างเธรดใหม่ได้

สิ่งถัดไปคือการซิงโครไนซ์ ระบบมัลติเธรดที่ทันสมัยแทบจะไม่รับประกันว่าเธรดทั้งหมดจะถูกดำเนินการในเวลาเดียวกัน หากคุณมีเธรดและกระบวนการที่ทำงานบนเครื่องมากกว่าแกนประมวลผล (ซึ่งส่วนใหญ่เป็นกรณี) เธรดจะไม่ได้รับ 100% ของเวลา CPU พวกเขาต้องแชร์ซีพียู ซึ่งหมายความว่าทุกเธรดจะได้รับเวลา CPU เล็กน้อยจากนั้นหลังจากที่มีการแชร์เธรดถัดไปจะรับ CPU ในช่วงเวลาสั้น ๆ ระบบไม่รับประกันว่าเธรดของคุณจะได้รับเวลา CPU เท่ากับเธรดอื่น ๆ ซึ่งหมายความว่าเธรดหนึ่งอาจรออีกเธรดหนึ่งจนเสร็จและอาจเกิดความล่าช้า

คุณควรจะดูว่ามันเป็นไปไม่ได้ที่จะเล่นโน้ตหลาย ๆ อันในเธรดเดียวดังนั้นเธรดจะเตรียมโน้ตทั้งหมดและจากนั้นจะให้คำสั่ง "เริ่มต้น" เท่านั้น

หากคุณจำเป็นต้องทำกับกระทู้อย่างน้อยนำมาใช้ใหม่หัวข้อ จากนั้นคุณไม่จำเป็นต้องมีหลายเธรดเท่าที่มีโน้ตในทั้งชิ้น แต่ต้องการเพียงเธรดให้มากที่สุดเท่าที่จำนวนสูงสุดของโน้ตที่เล่นในเวลาเดียวกัน


"เป็นไปได้หรือไม่ที่จะเล่นโน้ตหลาย ๆ อันในเธรดเดียวดังนั้นเธรดจะเตรียมโน้ตทั้งหมดและจากนั้นจะให้คำสั่ง" เริ่มต้น "เท่านั้น - นี่เป็นความคิดแรกของฉันเช่นกัน บางครั้งฉันสงสัยว่าถูกถล่มด้วยความเห็นเกี่ยวกับมัลติเธรด (เช่นprogrammers.stackexchange.com/questions/43321/ … ) ไม่ได้ทำให้โปรแกรมเมอร์จำนวนมากหลงทางในระหว่างการออกแบบ ฉันไม่อยากเชื่อเลยว่ากระบวนการที่มีขนาดใหญ่และล้ำสมัยใด ๆ ที่จบลงด้วยการตอบสนองความต้องการของเธรดจำนวนมาก ฉันขอแนะนำให้มองหาวิธีการแก้ปัญหาด้ายเดี่ยวอย่างตั้งใจ
user1172763

1

คำตอบในทางปฏิบัติคือถ้ามันใช้งานได้กับแอปพลิเคชันของคุณและตรงตามความต้องการในปัจจุบันของคุณคุณจะไม่ทำผิดเลย :) อย่างไรก็ตามถ้าคุณหมายถึง "นี่เป็นโซลูชันที่ปรับขนาดได้และมีประสิทธิภาพ การออกแบบทำให้สมมติฐานหลายอย่างเกี่ยวกับวิธีการทำงานของเธรดซึ่งอาจหรือไม่อาจเป็นจริงในสถานการณ์ที่แตกต่างกัน (ท่วงทำนองอีกต่อไปบันทึกย่อพร้อมกันเพิ่มเติมฮาร์ดแวร์ที่แตกต่างกัน ฯลฯ )

เป็นทางเลือกให้พิจารณาใช้วงเวลาและด้ายสระว่ายน้ำ การวนรอบเวลาทำงานในเธรดของตัวเองและตรวจสอบอย่างต่อเนื่องว่าจำเป็นต้องเล่นโน้ตหรือไม่ มันทำได้โดยการเปรียบเทียบเวลาของระบบกับเวลาที่เมโลดี้เริ่มต้น ระยะเวลาของแต่ละโน้ตสามารถคำนวณได้ง่ายมากจากจังหวะของทำนองและลำดับของโน้ต เนื่องจากต้องเล่นโน้ตใหม่ให้ยืมเธรดจากเธรดพูลและเรียกใช้play()ฟังก์ชันบนบันทึกย่อ การวนรอบเวลาสามารถทำงานได้หลายวิธี แต่สิ่งที่ง่ายที่สุดคือการวนซ้ำอย่างต่อเนื่องโดยมี sleeps สั้น ๆ (อาจเป็นระหว่างคอร์ด) เพื่อลดการใช้ CPU

ข้อดีของการออกแบบนี้คือจำนวนของเธรดจะต้องไม่เกินจำนวนสูงสุดของโน้ตพร้อมกัน + 1 (ลูปการจับเวลา) นอกจากนี้การวนรอบเวลาป้องกันคุณจาก slippages ในการกำหนดเวลาซึ่งอาจเกิดจากเวลาแฝงเธรด ประการที่สามจังหวะของทำนองนั้นไม่คงที่และสามารถเปลี่ยนแปลงได้โดยการเปลี่ยนการคำนวณเวลา

นอกจากนี้ฉันเห็นด้วยกับผู้แสดงความคิดเห็นคนอื่นว่าฟังก์ชั่นnote.play()นี้เป็น API ที่แย่มาก ๆ ที่ใช้งานได้ API เสียงที่สมเหตุสมผลใด ๆ จะช่วยให้คุณสามารถผสมและกำหนดเวลาบันทึกย่อได้อย่างยืดหยุ่นยิ่งขึ้น ที่กล่าวว่าบางครั้งเราต้องอยู่กับสิ่งที่เรามี :)


0

หน้าตาดีให้ฉันเป็นการดำเนินงานที่เรียบง่ายสมมติว่าเป็น API ที่คุณต้องใช้ คำตอบอื่น ๆ ครอบคลุมถึงสาเหตุที่นี่ไม่ใช่ API ที่ดีมากดังนั้นฉันไม่ได้พูดถึงเรื่องนั้นมากนักฉันแค่คิดว่ามันคือสิ่งที่คุณต้องใช้ชีวิตด้วย วิธีการของคุณจะใช้เธรดจำนวนมาก แต่บนพีซีที่ทันสมัยที่ไม่ควรกังวลตราบใดที่การนับเธรดอยู่ในหลักสิบ

สิ่งหนึ่งที่คุณควรทำถ้าเป็นไปได้ (เช่นเล่นจากไฟล์แทนจากผู้ใช้กดปุ่มคีย์บอร์ด) คือการเพิ่มเวลาแฝง ดังนั้นคุณเริ่มหัวข้อมันจะนอนจนกว่าจะถึงเวลาที่เฉพาะเจาะจงของนาฬิการะบบและเริ่มเล่นโน้ตในเวลาที่เหมาะสม (หมายเหตุบางครั้งการนอนหลับอาจถูกขัดจังหวะก่อนดังนั้นตรวจสอบนาฬิกาและนอนหลับมากขึ้นถ้าจำเป็น) แม้ว่าจะไม่มีการรับประกันว่าระบบปฏิบัติการจะดำเนินการเธรดต่อไปเมื่อการนอนหลับของคุณสิ้นสุดลง (เว้นแต่คุณจะใช้ระบบปฏิบัติการแบบเรียลไทม์) แต่ก็มีความแม่นยำมากกว่าถ้าคุณเพิ่งเริ่มกระทู้และเริ่มเล่นโดยไม่ตรวจสอบเวลา .

จากนั้นขั้นตอนถัดไปซึ่งทำให้สิ่งต่าง ๆ ยุ่งยากเล็กน้อย แต่ไม่มากเกินไปและจะช่วยให้คุณลดเวลาในการตอบสนองที่กล่าวถึงข้างต้นได้ให้ใช้กลุ่มเธรด นั่นคือเมื่อเธรดเสร็จโน้ตมันจะไม่ออก แต่จะรอให้โน้ตใหม่เล่นแทน และเมื่อคุณขอให้เริ่มเล่นโน้ตคุณต้องลองใช้เธรดฟรีจากพูลและเพิ่มเธรดใหม่ถ้าจำเป็นเท่านั้น สิ่งนี้จะต้องมีการสื่อสารระหว่างเธรดที่ง่าย ๆ แทนที่จะใช้วิธีดับเพลิงและลืมของคุณในปัจจุบัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.