ในทีมใหม่ของฉันที่ฉันจัดการรหัสส่วนใหญ่ของเราคือแพลตฟอร์มซ็อกเก็ต TCP และรหัสเครือข่าย http C ++ ทั้งหมด ส่วนใหญ่มาจากผู้พัฒนารายอื่นที่ออกจากทีมไป นักพัฒนาปัจจุบันของทีมนั้นฉลาดมาก แต่ส่วนใหญ่เป็นรุ่นรองในแง่ของประสบการณ์
ปัญหาที่ใหญ่ที่สุดของเรา: ข้อบกพร่องที่เกิดขึ้นพร้อมกันแบบมัลติเธรด ไลบรารีคลาสของเราส่วนใหญ่เขียนเป็นแบบอะซิงโครนัสโดยใช้คลาสเธรดพูลบางคลาส เมธอดบนไลบรารีคลาสมักเข้าคิวเพื่อรัน taks ที่ยาวบนเธรดพูลจากเธรดหนึ่งและจากนั้นเมธอดการเรียกกลับของคลาสนั้นจะถูกเรียกใช้บนเธรดอื่น เป็นผลให้เรามีข้อบกพร่องกรณีขอบจำนวนมากที่เกี่ยวข้องกับข้อสันนิษฐานเกลียวที่ไม่ถูกต้อง ซึ่งส่งผลให้ข้อบกพร่องที่ลึกซึ้งที่มีมากกว่าส่วนที่สำคัญและล็อคเพื่อป้องกันปัญหาการเกิดพร้อมกัน
สิ่งที่ทำให้ปัญหาเหล่านี้ยากขึ้นคือความพยายามแก้ไขมักไม่ถูกต้อง ข้อผิดพลาดบางประการที่ฉันสังเกตเห็นว่าทีมพยายาม (หรือภายในรหัสเดิม) รวมถึงสิ่งต่อไปนี้:
ข้อผิดพลาดทั่วไป # 1 - แก้ไขปัญหาการเกิดพร้อมกันโดยเพียงแค่ใส่ล็อกรอบ ๆ ข้อมูลที่ใช้ร่วมกัน แต่ลืมเกี่ยวกับสิ่งที่เกิดขึ้นเมื่อวิธีการไม่ได้รับการเรียกในลำดับที่คาดหวัง นี่เป็นตัวอย่างง่ายๆ:
void Foo::OnHttpRequestComplete(statuscode status)
{
m_pBar->DoSomethingImportant(status);
}
void Foo::Shutdown()
{
m_pBar->Cleanup();
delete m_pBar;
m_pBar=nullptr;
}
ดังนั้นตอนนี้เรามีข้อผิดพลาดที่สามารถเรียกปิดได้ในขณะที่ OnHttpNetworkRequestComplete กำลังเกิดขึ้น ผู้ทดสอบพบข้อผิดพลาดจับการถ่ายโอนข้อมูลความผิดพลาดและกำหนดข้อผิดพลาดให้กับนักพัฒนา เขาก็แก้ไขข้อผิดพลาดเช่นนี้
void Foo::OnHttpRequestComplete(statuscode status)
{
AutoLock lock(m_cs);
m_pBar->DoSomethingImportant(status);
}
void Foo::Shutdown()
{
AutoLock lock(m_cs);
m_pBar->Cleanup();
delete m_pBar;
m_pBar=nullptr;
}
การแก้ไขด้านบนดูดีจนกระทั่งคุณรู้ว่ามีเคสขอบที่บอบบางยิ่งขึ้น จะเกิดอะไรขึ้นถ้ามีการเรียกใช้การปิดเครื่องก่อนที่ OnHttpRequestComplete จะได้รับการเรียกคืน ตัวอย่างโลกแห่งความเป็นจริงที่ทีมของฉันมีความซับซ้อนยิ่งขึ้นและกรณีขอบก็ยากที่จะมองเห็นในระหว่างกระบวนการตรวจสอบรหัส
สามัญข้อผิดพลาด # 2 - แก้ไขปัญหาการหยุดชะงักโดยสุ่มล็อกออกจากล็อกรอให้เธรดอื่นเสร็จสิ้นจากนั้นจึงล็อคอีกครั้ง
ข้อผิดพลาดทั่วไป # 3 - แม้ว่าจะนับการอ้างอิงวัตถุลำดับการปิดเครื่อง "ปล่อย" เป็นตัวชี้ แต่ลืมที่จะรอเธรดที่ยังคงทำงานอยู่เพื่อปล่อยมัน ดังนั้นส่วนประกอบต่างๆจะถูกปิดอย่างสมบูรณ์จากนั้นการเรียกกลับปลอมหรือการโทรกลับล่าช้าจะถูกเรียกใช้บนวัตถุที่อยู่ในสถานะที่ไม่ต้องการการเรียกอีกต่อไป
มีกรณีขอบอื่น ๆ แต่บรรทัดล่างคือ:
การเขียนโปรแกรมแบบมัลติเธรดเป็นเรื่องยากธรรมดาแม้แต่คนฉลาด
เมื่อฉันตรวจพบข้อผิดพลาดเหล่านี้ฉันใช้เวลาพูดคุยข้อผิดพลาดกับผู้พัฒนาแต่ละรายเพื่อพัฒนาการแก้ไขที่เหมาะสมยิ่งขึ้น แต่ฉันสงสัยว่าพวกเขามักจะสับสนในการแก้ปัญหาแต่ละเรื่องเนื่องจากรหัสมรดกจำนวนมหาศาลที่การแก้ไข "ถูกต้อง" จะเกี่ยวข้องกับการสัมผัส
เรากำลังจะจัดส่งเร็ว ๆ นี้และฉันมั่นใจว่าแพทช์ที่เราใช้จะมีไว้สำหรับการเปิดตัวในอนาคต หลังจากนั้นเราจะมีเวลาในการปรับปรุง code base และ refactor หากจำเป็น เราไม่มีเวลาเขียนทุกอย่างอีกแล้ว และรหัสส่วนใหญ่ก็ไม่ได้แย่ขนาดนั้นทั้งหมด แต่ฉันต้องการ refactor code เพื่อหลีกเลี่ยงปัญหาการทำเกลียวทั้งหมด
วิธีหนึ่งที่ฉันกำลังพิจารณาก็คือ สำหรับคุณลักษณะแพลตฟอร์มที่สำคัญแต่ละรายการให้มีเธรดเดี่ยวโดยเฉพาะซึ่งกิจกรรมและการโทรกลับเครือข่ายทั้งหมดจะถูกบันทึกไว้บน คล้ายกับอพาร์ทเมนท์ COM เธรดใน Windows ด้วยการใช้วนรอบข้อความ การดำเนินการบล็อกแบบยาวยังคงสามารถส่งไปยังเธรดพูลงานได้ แต่การเรียกกลับที่เสร็จสิ้นแล้วจะถูกเรียกใช้บนเธรดของคอมโพเนนต์ ส่วนประกอบอาจแบ่งปันกระทู้เดียวกันได้ด้วยซ้ำ จากนั้นไลบรารีคลาสทั้งหมดที่ทำงานภายในเธรดสามารถถูกเขียนภายใต้สมมติฐานของโลกเธรดเดียว
ก่อนที่ฉันจะลงไปที่เส้นทางนั้นฉันก็สนใจเช่นกันถ้ามีเทคนิคมาตรฐานอื่น ๆ หรือรูปแบบการออกแบบสำหรับจัดการกับปัญหาแบบมัลติเธรด และฉันต้องเน้น - บางสิ่งบางอย่างนอกเหนือจากหนังสือที่อธิบายพื้นฐานของ mutexes และ semaphores คุณคิดอย่างไร?
ฉันยังสนใจในวิธีการอื่นเพื่อนำไปสู่กระบวนการฟื้นฟู รวมถึงสิ่งต่อไปนี้:
วรรณกรรมหรือเอกสารเกี่ยวกับรูปแบบการออกแบบรอบ ๆ เธรด มีอะไรบางอย่างที่นอกเหนือจากการแนะนำให้รู้จักกับ mutexes และเซมาฟอร์ เราไม่จำเป็นต้องขนานใหญ่ทั้งสองวิธีเพียงแค่การออกแบบรูปแบบวัตถุนั้นเพื่อจัดการกับเหตุการณ์ไม่ตรงกันจากหัวข้ออื่น ๆได้อย่างถูกต้อง
วิธีทำแผนภาพเกลียวของส่วนประกอบต่าง ๆ เพื่อให้ง่ายต่อการศึกษาและพัฒนาวิธีแก้ปัญหา (นั่นคือเทียบเท่า UML สำหรับการพูดคุยหัวข้อข้ามวัตถุและชั้นเรียน)
ให้ความรู้แก่ทีมพัฒนาของคุณเกี่ยวกับปัญหาด้วยรหัสหลายเธรด
คุณจะทำอย่างไร