คุณเรียนรู้บทเรียนใดจากโปรเจ็กต์ที่เกือบ / ล้มเหลวจริง ๆ เนื่องจากมัลติเธรดไม่ดี [ปิด]


11

คุณเรียนรู้บทเรียนใดจากโปรเจ็กต์ที่เกือบ / ล้มเหลวจริง ๆ เนื่องจากมัลติเธรดไม่ดี

บางครั้งกรอบกำหนดรูปแบบเกลียวบางอย่างที่ทำให้สิ่งที่ลำดับความสำคัญยากที่จะได้รับ

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

ฉันพบว่าฉันเก่งในการแก้ปัญหาแบบมัลติเธรดซึ่งมีทางแยกอย่างง่าย / เข้าร่วมและที่ซึ่งข้อมูลเดินทางไปในทิศทางเดียวเท่านั้น (ในขณะที่สัญญาณสามารถเดินทางในทิศทางวงกลม)

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

ตอนที่ฉันออกจากโครงการนั้นไปอีกโครงการหนึ่งมีปัญหาการหยุดชะงักทุกที่ ฉันได้ยินมาว่า 2-3 เดือนต่อมาผู้พัฒนารายอื่นหลายคนจัดการเพื่อแก้ไขปัญหาการหยุดชะงักทั้งหมดจนถึงจุดที่สามารถส่งมอบให้กับลูกค้าได้ ฉันไม่เคยพบว่าความรู้ที่หายไปนั้นขาดไป

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

ที่เพิ่ม

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

ผู้ร่วมให้ข้อมูล:

(ผู้มีส่วนร่วมไม่จำเป็นต้องใช้ตัวอย่างจริง / ส่วนตัวตัวอย่างบทเรียนจากตัวอย่างเล็ก ๆ น้อย ๆ หากคุณได้รับการตัดสินว่ามีความน่าเชื่อถือเรายินดีต้อนรับด้วยเช่นกัน)



ฉันคิดว่าการสามารถ 'คิดในหัวข้อ' เป็นความสามารถและบางสิ่งบางอย่างน้อยที่สามารถเรียนรู้ได้เพราะขาดถ้อยคำที่ดีกว่า ฉันรู้ว่านักพัฒนาจำนวนมากที่ทำงานกับระบบคู่ขนานมาเป็นเวลานาน แต่พวกเขาทำให้หายใจไม่ออกถ้าข้อมูลต้องไปในทิศทางเดียวมากกว่า
dauphic

คำตอบ:


13

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

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


+1 สำหรับ "เพื่อไม่แชร์หน่วยความจำ (หรือทรัพยากรอื่น ๆ ) ระหว่างเธรดยกเว้นการส่งข้อความซึ่งจะต้องไม่ตรงกัน"
Nemanja Trifunovic

1
เพียงวิธี? ชนิดข้อมูลที่เปลี่ยนแปลงไม่ได้?
Aaronaught

is that in a multithreaded program the scheduler is a sneaky swine that hates you.- ไม่มันไม่ได้ก็ไม่ตรงกับสิ่งที่คุณบอกว่ามันจะทำ :)
mattnz

@Aaraught: ค่าทั่วโลกผ่านการอ้างอิงถึงแม้ว่าไม่เปลี่ยนรูปยังคงต้องการ GC ทั่วโลกและแนะนำทรัพยากรทั่วโลกจำนวนมากอีกครั้ง ความสามารถในการใช้การจัดการหน่วยความจำต่อเธรดเป็นเรื่องที่ดีเพราะมันช่วยให้คุณกำจัดการล็อกระดับโลกทั้งหมด
Donal Fellows

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

6

นี่คือบทเรียนพื้นฐานเล็กน้อยที่ฉันนึกได้ตอนนี้ (ไม่ใช่จากโครงการที่ล้มเหลว แต่มาจากปัญหาจริงที่เห็นในโครงการจริง):

  • พยายามหลีกเลี่ยงการบล็อกการโทรใด ๆ ในขณะที่ถือครองทรัพยากรที่ใช้ร่วมกัน รูปแบบการหยุดชะงักทั่วไปคือ mutab คว้าเธรดทำให้การติดต่อกลับบล็อกการติดต่อกลับบน mutex เดียวกัน
  • ปกป้องการเข้าถึงโครงสร้างข้อมูลที่ใช้ร่วมกันด้วยส่วน mutex / critical (หรือใช้โครงสร้างล็อคฟรี - แต่ไม่ต้องคิดค้นของคุณเอง!)
  • อย่าใช้ atomicity - ใช้ atomic API (เช่น InterlockedIncrement)
  • RTFM เกี่ยวกับความปลอดภัยของเธรดของไลบรารีวัตถุหรือ API ที่คุณใช้
  • ใช้ประโยชน์จากการซิงโคไนเซอร์ดั้งเดิมที่มีอยู่เช่นเหตุการณ์เซมาฟอร์ (แต่ให้ความใส่ใจอย่างใกล้ชิดเมื่อใช้พวกเขาที่คุณรู้ว่าคุณอยู่ในสถานะดี - ฉันเคยเห็นตัวอย่างของเหตุการณ์ที่ส่งสัญญาณในสถานะที่ไม่ถูกต้องเช่นเหตุการณ์หรือข้อมูลอาจสูญหายได้)
  • สมมติว่าเธรดสามารถดำเนินการพร้อมกันและ / หรือในลำดับใดก็ได้และบริบทนั้นอาจสลับไปมาระหว่างเธรดได้ตลอดเวลา (ยกเว้นว่าอยู่ภายใต้ระบบปฏิบัติการที่รับประกันอื่น ๆ )

6
  • โครงการGUIทั้งหมดของคุณควรถูกเรียกจากเธรดหลักเท่านั้น โดยทั่วไปคุณไม่ควรใส่ ".net" "เรียกใช้" ใน GUI ของคุณ มัลติเธรดควรติดอยู่ในโครงการแยกต่างหากที่จัดการการเข้าถึงข้อมูลที่ช้าลง

เราสืบทอดส่วนที่โครงการ GUI ใช้เธรดโหล มันไม่ได้ให้อะไรนอกจากมีปัญหา Deadlocks, ปัญหาการแข่งรถ, ข้ามเธรด GUI โทร ...


"project" หมายถึง "assembly" หรือไม่ ฉันไม่เห็นว่าการกระจายคลาสในแอสเซมบลีจะทำให้เกิดปัญหาการเธรดได้อย่างไร
nikie

ในโครงการของฉันมันแน่นอนการชุมนุม แต่ประเด็นหลักคือรหัสทั้งหมดในโฟลเดอร์เหล่านั้นจะต้องถูกเรียกจากเธรดหลักไม่มีข้อยกเว้น
Carra

ฉันไม่คิดว่าโดยทั่วไปจะใช้กฎนี้ ใช่คุณไม่ควรเรียกรหัส GUI จากเธรดอื่น แต่วิธีที่คุณแจกจ่ายคลาสไปยังโฟลเดอร์ / โครงการ / ชุดประกอบเป็นการตัดสินใจที่เป็นอิสระ
nikie

1

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

ใช้สิ่งเหล่านั้นมันจะกำจัดความเจ็บปวดออกไปมากมาย

(และใช่สิ่งนี้ฉันได้เรียนรู้จากโครงการ :))


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

1

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

  • กฎ # 1: KISS - หากไม่ต้องการเธรดอย่าหมุนหนึ่งอัน ทำให้เป็นอนุกรมมากที่สุด
  • # 2 กฎ: อย่าทำลาย # 1
  • # 3 หากคุณไม่สามารถพิสูจน์ได้ผ่านการตรวจสอบมันถูกต้องมันไม่ได้

+1 สำหรับกฎ 1 ฉันกำลังทำงานในโครงการที่เริ่มบล็อกจนกระทั่งเธรดอื่นเสร็จสมบูรณ์ - โดยพื้นฐานแล้วการเรียกเมธอด! โชคดีที่เราตัดสินใจต่อแนวทางนั้น
Michael K

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

1

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

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

ในคำอื่น ๆ ปัญหาเธรดที่เลวร้ายที่สุดบางอย่างเกิดจากการพยายาม overkill เพื่อหลีกเลี่ยงปัญหาเธรด


0

ลองทำอีกครั้ง

อย่างน้อยสำหรับฉันสิ่งที่สร้างความแตกต่างคือการฝึกฝน หลังจากทำงานหลายเธรดและกระจายงานไม่กี่ครั้งคุณก็จะได้รับมัน

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

อีกสิ่งหนึ่งที่เรียนรู้เพิ่มเติมคือล็อคโครงสร้างข้อมูลฟรี

ฉันคิดว่าคำถามนี้สามารถปรับปรุงได้จริง ๆ ถ้าคุณระบุกรอบงาน . NET กลุ่มสระว่ายน้ำและคนทำงานพื้นหลังจะแตกต่างจาก QThread ตัวอย่างเช่น มี gotchas เฉพาะบางแพลตฟอร์มเสมอ


ฉันสนใจที่จะได้ยินเรื่องราวจากกรอบการทำงานใด ๆ เพราะฉันเชื่อว่ามีสิ่งที่ต้องเรียนรู้จากแต่ละเฟรมเวิร์กโดยเฉพาะอย่างยิ่งสิ่งที่ฉันไม่เคยสัมผัส
rwong

1
debuggers ส่วนใหญ่ไร้ประโยชน์ในสภาพแวดล้อมแบบมัลติเธรด
Pemdas

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

0

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


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

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

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

@nikie: หากล็อคต้องถูกเก็บไว้ในระหว่างการโทรกลับส่วนที่เหลือของ API จะต้องได้รับการออกแบบให้เป็น reentrant (ยาก!) หรือความจริงที่ว่าคุณกำลังถือล็อคต้องเป็นส่วนหนึ่งของ API ( โชคร้าย แต่บางครั้งคุณก็ทำได้)
Donal Fellows

@ Donal Fellows: หากต้องล็อคไว้ระหว่างโทรกลับฉันต้องบอกว่าคุณมีข้อบกพร่องด้านการออกแบบ หากไม่มีวิธีอื่นที่จริงแล้วใช่โดยทั้งหมดหมายความว่าเอกสาร! เช่นเดียวกับที่คุณทำเอกสารหากการโทรกลับจะถูกเรียกในเธรดพื้นหลัง นั่นเป็นส่วนหนึ่งของอินเทอร์เฟซ
nikie
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.