- มีการตั้งข้อสังเกตว่าเป็นเรื่องน่าเสียดายที่ตำรา The Art of Multiprocessor Programming ไม่กล่าวถึงแนวคิด futex
- Futex เป็นองค์ประกอบหลักของ การซิงโครไนซ์ที่มีประสิทธิภาพ ในการเขียนโปรแกรมแบบขนานสมัยใหม่ และให้ประสิทธิภาพดีกว่าล็อกแบบ System V เดิม
- futex มีโครงสร้างที่ แยกการได้มาของล็อกออกจากการรอ/การปลุก เพื่อลด system call และ overhead ที่ไม่จำเป็น
- มีตัวอย่างและคำอธิบายเทคนิคในการนำ futex ไปใช้สร้าง concurrency primitives หลากหลายแบบด้วยตนเอง เช่น spinlock, mutex, และ recursive lock
- ผู้เขียนชี้ว่าหนังสือไม่ได้ครอบคลุม วิธีการซิงโครไนซ์สมัยใหม่ที่จำเป็นต่อการทำงานวิศวกรรมจริง สะท้อนช่องว่างระหว่างแวดวงวิชาการกับภาคปฏิบัติ
บทนำ
- Phil Eaton เริ่มต้นชมรมอ่านหนังสือ 'The Art of Multiprocessor Programming, 2nd Edition'
- แม้หนังสือเล่มนี้จะถูกมองว่าเป็นตำราทรงอิทธิพลในด้านการเขียนโปรแกรมแบบขนาน แต่ผู้เขียนชี้ถึง การขาดความเป็นประโยชน์เชิงปฏิบัติ ของเนื้อหา
- โดยเฉพาะอย่างยิ่ง หนังสือระบุว่ามุ่งเป้าไปที่นักศึกษาปริญญาตรีปี 4 และนักศึกษาบัณฑิตศึกษา แต่กลับไม่กล่าวถึง futex ซึ่งเป็น เทคนิคการซิงโครไนซ์หลัก
futex คืออะไร – ทำไมจึงสำคัญ
- futex ย่อมาจาก “fast user space mutex” แต่ในความเป็นจริงแล้วมันไม่ใช่ mutex โดยตรง หากเป็น primitive สำหรับการซิงโครไนซ์ที่ระบบปฏิบัติการสนับสนุน เพื่อใช้สร้างล็อกสมัยใหม่
- ในอดีต ล็อกส่วนใหญ่สร้างบน semaphore ของ System V IPC ทำให้มี ข้อจำกัดด้านประสิทธิภาพและการขยายตัว
- เมื่อ Linux นำ futex มาใช้ในปี 2002 ก็แสดงให้เห็นว่า เร็วกว่า System V lock ถึง 20~120 เท่าในสภาพแวดล้อมที่มีงานพร้อมกัน 1000 งาน
- ระบบปฏิบัติการอื่นอย่าง Windows (ปี 2012) และ macOS (ปี 2016) ก็ได้นำกลไกคล้ายกันมาใช้
- ปัจจุบัน ล็อกใน system library ที่ใช้งานแพร่หลายอย่าง pthreads ก็ใช้ futex
หลักการทำงานและจุดแตกต่างของ futex
- semaphore แบบเดิมผูกการล็อกเข้ากับการรอ แต่ futex แยกการได้มาของล็อกออกจากการรอ/การปลุก
- ด้วยเหตุนี้จึงลด ดีเลย์และ system call ที่ไม่จำเป็น ได้ และหากมั่นใจว่าไม่มีเธรดที่รออยู่ในตอนปลดล็อก ก็ไม่จำเป็นต้องเข้าสู่เคอร์เนล
- การเรียก wait ของ futex จะทำให้ “รอเฉพาะเมื่อค่าที่ address หน่วยความจำหนึ่งอยู่ในสถานะที่ต้องการเท่านั้น” และยังรองรับ timeout
- การเรียก wake ของ futex จะปลุกจำนวนเธรดตามต้องการจากรายการรอภายในที่ผูกกับ address หน่วยความจำหนึ่ง
- มัน กำหนดให้ตรวจสอบค่าจริงของ address หน่วยความจำ เพื่อลดการรอที่ไม่จำเป็นเมื่อสถานะเปลี่ยนไปแล้ว
การใช้งาน futex ในทางปฏิบัติ – ลงมือสร้างเอง
- futex เป็น primitive ระดับล่าง ดังนั้นจึงต้องใช้ชนิดข้อมูล
atomic โดยคำนึงถึง ประเด็นลำดับการทำงานของหน่วยความจำจากคอมไพเลอร์และฮาร์ดแวร์
- บน Linux ต้องเรียก futex system call โดยตรงผ่าน
syscall ส่วนบน macOS ใช้อินเทอร์เฟซ __ulock (ปัจจุบันมี API ที่ใช้ง่ายกว่านี้เพิ่มเข้ามาแล้ว)
- โดยพื้นฐานแล้ว การรอด้วย futex จะคืนค่า 0 เมื่อสำเร็จ และคืน error code เมื่อไม่สำเร็จ เช่น timeout
- โอเปอเรชันหลักที่อิงกับ futex:
h4x0r_futex_wait_timespec() : รอเมื่อค่าที่คาดไว้ตรงกัน และสามารถกำหนด timeout ได้
h4x0r_futex_wake() : ปลุกผู้รอ 1 รายหรือทั้งหมด
ตัวอย่างใช้งานจริงของการสร้าง mutex/spinlock/recursive lock
spinlock
- ล็อกแบบพื้นฐานที่สุด ทำงานด้วย บิตเดียว (
atomic_fetch_or)
- จะวนลูปไม่สิ้นสุด (“spin”) จนกว่าจะได้ล็อก แต่ในกรณีที่มีการแข่งขันสูงจะเกิด การสิ้นเปลือง CPU และยังมีปัญหาเชิงโครงสร้าง เช่น ปลดล็อกผิด และเสี่ยง deadlock เมื่อเรียกซ้ำแบบ recursive
hybrid mutex (‘unsafe’ mutex)
- โดยทั่วไปจะ ลองด้วย spinlock ก่อน และหากล้มเหลวจำนวนหนึ่งครั้งจึงสลับไปใช้ futex เพื่อบล็อกอย่างมีประสิทธิภาพ
- หากไม่มีผู้รอ ก็หลีกเลี่ยง system call ที่ไม่จำเป็นได้ และในกรณีมีผู้รอก็สามารถลดจำนวน wake system call ให้น้อยที่สุด
- เนื่องจากยังขาดการตรวจสอบ ownership และการจัดการ recursive อย่างเคร่งครัด จึงใช้ชื่อว่า “unsafe”
mutex แบบนับจำนวนผู้รอ
- ใช้หนึ่งบิตสำหรับสถานะล็อก และใช้บิตที่เหลือสำหรับ นับจำนวนผู้รอ เพื่อลด wake system call ที่ไม่จำเป็น
- อย่างไรก็ตาม ยังไม่มีการจัดการ ownership และ recursive
mutex ที่รวมการจัดการ ownership
- ใช้ค่า
pthread_t เพื่อ ติดตามเจ้าของล็อกและสถานะได้อย่างชัดเจน ทำให้ตรวจจับปัญหาเมื่อ unlock ผิดหรือใช้งานแบบ recursive ได้
- ทั้งการได้มาของล็อก การปลดล็อก และการจัดการผู้รอ ล้วนควบคุมด้วย atomic operation อย่างเคร่งครัด
recursive lock
- เพิ่ม ตัวนับจำนวนการซ้อนกัน (depth) แยกตามเธรด เพื่อให้เธรดเดียวกันสามารถได้ล็อกซ้ำแบบซ้อนได้
- เมื่อ unlock จะลดค่า depth และเมื่อเป็น 0 จึงปลดล็อกจริงพร้อมปลุกผู้รอ
- แต่ละการทำงานถูกสร้างขึ้นด้วย atomic operation และการตรวจสอบ ownership อย่างเคร่งครัด
โจทย์ที่ยังเหลือและความเป็นจริงของงานวิศวกรรม
- หากเธรดเจ้าของล็อกจบการทำงานอย่างผิดปกติหรือหยุดไป จะต้องมี รายการจัดการแยกต่างหาก, callback ตอนสิ้นสุด ฯลฯ เพิ่มเติมเพื่อดูแลล็อก
- แม้แต่เมื่อใช้ mutex ที่แชร์ข้ามโปรเซส ก็ยังต้องคำนึงถึงการจัดการการเปลี่ยนแปลงสถานะเพิ่มเติม
- POSIX RW lock ไม่ได้กำหนดพฤติกรรมการซ้อนแบบ recursive ไว้อย่างชัดเจน และยังต่างกันไปในแต่ละ implementation ทำให้รับประกันความปลอดภัยได้ยากในทางปฏิบัติ
- ผู้เขียนวิจารณ์ว่าหนังสือไม่ได้รวม ประเด็น concurrency ที่สำคัญจริงในการใช้งานจริง (futex, recursive lock, async runtime ฯลฯ) ไว้ในหลักสูตร
บทสรุป
- 'The Art of Multiprocessor Programming' เอนเอียงไปทางประวัติศาสตร์หรือทฤษฎี จนไม่สามารถถ่ายทอดความรู้ภาคปฏิบัติที่สำคัญของการเขียนโปรแกรมแบบขนานสมัยใหม่ได้อย่างเพียงพอ
- หากไม่อธิบาย องค์ประกอบการซิงโครไนซ์หลักอย่าง futex ที่ระบบใช้งานจริงอย่างเหมาะสม ก็อาจก่อผลเสียในทางปฏิบัติต่อผู้เรียนรุ่นต่อไป
- ผู้เขียนเน้นย้ำถึงความจำเป็นในการสะท้อนแนวคิดสมัยใหม่และเสริมเนื้อหาให้ใช้งานได้จริง
เอกสารอ้างอิง
ยังไม่มีความคิดเห็น