ฉันจะทำให้ข้อความที่ผ่านระหว่างเธรดในเอ็นจินแบบมัลติเธรดยุ่งยากน้อยลงได้อย่างไร?


18

เอ็นจิ้น C ++ ที่ฉันกำลังทำงานอยู่นั้นแบ่งออกเป็นหลายเธรดขนาดใหญ่ (สำหรับการสร้างเนื้อหาขั้นตอนของฉัน), การเล่นเกม (สำหรับ AI, สคริปต์, การจำลอง), ฟิสิกส์และการแสดงผล

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

ในช่วงต้นกระบวนการและฉันสังเกตเห็นสิ่งที่สอง:

  1. ระบบการส่งข้อความยุ่งยาก การสร้างชนิดข้อความใหม่หมายถึงคลาสย่อยคลาสคลาสพื้นฐานของข้อความการสร้าง enum ใหม่สำหรับประเภทของมันและการเขียนตรรกะสำหรับวิธีที่เธรดควรตีความชนิดข้อความใหม่ มันเป็นความเร็วในการพัฒนาและมีแนวโน้มที่จะเกิดข้อผิดพลาดแบบผิดพลาด (Sidenote- การทำงานกับสิ่งนี้ทำให้ฉันรู้สึกว่าภาษาไดนามิกที่ยอดเยี่ยมสามารถเป็นเช่นนั้นได้!)

    มีวิธีที่ดีกว่าในการทำเช่นนี้? ฉันควรใช้บางอย่างเช่น boost :: bind เพื่อทำให้เป็นอัตโนมัติหรือไม่ ฉันเป็นห่วงว่าถ้าฉันทำอย่างนั้นฉันจะสูญเสียความสามารถในการพูดจัดเรียงข้อความตามประเภทหรือบางสิ่งบางอย่าง ไม่แน่ใจว่าการจัดการแบบนั้นจะจำเป็นหรือไม่

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

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

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


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

2
หากคุณต้องการที่จะมีส่วนร่วมในการสนทนาทั่วไปมากขึ้นผมจะแนะนำให้คุณลองโพสต์นี้มากกว่าในฟอรั่มที่gamedev.net ดังที่ Josh กล่าวเนื่องจาก "คำถาม" ของคุณไม่ใช่คำถามที่เฉพาะเจาะจงจึงเป็นการยากที่จะรองรับในรูปแบบ StackExchange
Cypher

ขอบคุณสำหรับคำติชมครับ! ฉันหวังว่าจะมีใครบางคนที่มีความรู้มากกว่านี้อาจมีทรัพยากร / ประสบการณ์ / กระบวนทัศน์เดียวที่อาจแก้ไขปัญหาของฉันหลาย ๆ อย่างพร้อมกัน ฉันได้รับความรู้สึกว่ามีความคิดที่สำคัญอย่างหนึ่งสามารถสังเคราะห์ปัญหาต่าง ๆ ของฉันเป็นสิ่งหนึ่งที่ฉันหายไปและฉันคิดว่าใครบางคนที่มีประสบการณ์มากกว่าที่ฉันอาจจะจำได้ว่า ... แต่อาจจะไม่ !
Raptormeat

ฉันเปลี่ยนชื่อของคุณให้เฉพาะเจาะจงมากขึ้นต่อการส่งข้อความเนื่องจากคำถามประเภท "เคล็ดลับ" บ่งบอกว่าไม่มีปัญหาเฉพาะในการแก้ไข (และดังนั้นวันนี้ฉันจึงปิดเพราะ "ไม่ใช่คำถามจริง")
Tetrad

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

คำตอบ:


10

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

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


1
คำแนะนำใด ๆ สำหรับการรวมเธรด libs ที่เฉพาะเจาะจงเพื่อตรวจสอบ?
imre

นิค - ขอบคุณมากสำหรับคำตอบ สำหรับประเด็นแรกของคุณ - ฉันคิดว่านั่นเป็นความคิดที่ดีและอาจเป็นทิศทางที่ฉันจะเข้าไปในตอนนี้มันเร็วพอที่ฉันยังไม่รู้ว่าจะต้องมีบัฟเฟอร์สองเท่า ฉันจะจำสิ่งนี้ไว้ในใจว่ามันจะแข็งตัวตลอดเวลา ถึงจุดที่สองของคุณ - ขอบคุณสำหรับคำแนะนำ! ใช่ประโยชน์ของงานด้ายมีความชัดเจน ฉันจะอ่านลิงก์ของคุณและคิดดู ไม่แน่ใจ 100% ว่าจะใช้งานได้สำหรับฉัน / จะทำให้มันใช้งานได้สำหรับฉันหรือไม่ แต่ฉันจะให้ความคิดที่จริงจัง ขอบคุณ!
Raptormeat

1
@imre ลองดูห้องสมุด Boost - พวกเขามีฟิวเจอร์สซึ่งเป็นวิธีที่ดี / ง่ายในการเข้าถึงสิ่งเหล่านี้
Jonathan Dickinson

5

คุณถามถึงการออกแบบแบบมัลติเธรดที่แตกต่างกัน เพื่อนของฉันบอกฉันเกี่ยวกับวิธีการนี้ซึ่งฉันคิดว่าค่อนข้างเท่ห์

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


4

เราพบปัญหาเดียวกันกับ C # เท่านั้น หลังจากคิดมานานและยากลำบากเกี่ยวกับความง่าย (หรือขาดมัน) ในการสร้างข้อความใหม่สิ่งที่ดีที่สุดที่เราทำได้คือการสร้างตัวสร้างรหัสให้พวกเขา มันค่อนข้างน่าเกลียด แต่ใช้งานได้: มีเพียงคำอธิบายเนื้อหาข้อความเท่านั้นมันสร้างคลาสของข้อความ, enums, ตัวยึดตำแหน่งการจัดการรหัส ฯลฯ - รหัสทั้งหมดนี้เกือบจะเหมือนกันทุกครั้งและพิมพ์ผิดได้ง่าย

ฉันไม่พอใจกับสิ่งนี้ทั้งหมด แต่มันดีกว่าเขียนโค้ดทั้งหมดด้วยมือ

สำหรับข้อมูลที่แชร์นั้นคำตอบที่ดีที่สุดก็คือ "ขึ้นอยู่กับ" แต่โดยทั่วไปถ้าข้อมูลบางอย่างถูกอ่านบ่อยและเป็นที่ต้องการของหลาย ๆ เธรดการแชร์มันก็คุ้มค่า เพื่อความปลอดภัยของเธรดทางออกที่ดีที่สุดของคุณคือการทำให้ไม่เปลี่ยนรูปแต่ถ้านั่นไม่ใช่คำถาม Mutex อาจทำ ใน C # มีReaderWriterLockSlimคลาสที่ออกแบบมาเป็นพิเศษสำหรับกรณีดังกล่าว ฉันแน่ใจว่าเทียบเท่า C ++

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


ขอบคุณสำหรับข้อมูลที่ดีทั้งหมด! บิตสุดท้ายเกี่ยวกับตัวจัดการคล้ายกับสิ่งที่ฉันพูดถึงเกี่ยวกับการใช้การผูกหรือ functors การส่งผ่านฟังก์ชัน ฉันชอบความคิด - ฉันอาจลองดูและดูว่ามันแย่หรือยอดเยี่ยม: D อาจเริ่มต้นด้วยเพียงแค่สร้างคลาส CallDelegateMessage แล้วจุ่มนิ้วเท้าลงไปในน้ำ
Raptormeat

1

ฉันอยู่ในช่วงการออกแบบของโค้ดเกมที่เป็นเกลียวดังนั้นฉันจึงสามารถแบ่งปันความคิดของฉันเท่านั้นไม่ใช่ประสบการณ์จริง ด้วยที่กล่าวว่าฉันคิดตามบรรทัดต่อไปนี้:

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

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

โดยรวมแล้วฉันคิดว่าวิธีการนี้จะให้ผลดีกับระบบการรวมเธรด ชิ้นส่วนที่มีปัญหาคือ:

  • การซิงค์เธรดการอัพเดต (ตรวจสอบว่าไม่มีหลายเธรดพยายามอัพเดตชุดข้อมูลเดียวกัน)
  • ตรวจสอบให้แน่ใจว่าในเฟสการอ่านไม่มีเธรดสามารถเขียนข้อมูลที่แชร์ได้โดยไม่ตั้งใจ ฉันกลัวว่าจะมีพื้นที่มากเกินไปสำหรับความผิดพลาดในการเขียนโปรแกรมและฉันไม่แน่ใจว่าจะสามารถจับพวกมันได้ง่ายเพียงใดด้วยเครื่องมือดีบัก
  • การเขียนโค้ดในลักษณะที่คุณไม่สามารถเชื่อถือได้จากผลการสืบค้นระดับกลางที่มีอยู่สำหรับการอ่านได้ทันที นั่นคือคุณไม่สามารถเขียนได้x += 2; if (x > 5) ...ถ้าแบ่งปัน x คุณต้องทำสำเนาโลคัลของ x หรือสร้างคำร้องขอให้อัพเดตและทำตามเงื่อนไขในการรันครั้งถัดไปเท่านั้น หลังจะหมายถึงรัฐด้ายท้องถิ่นเพิ่มเติมเพิ่มเติมรักษารหัสสำเร็จรูป
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.