ทำไม Python Async ถึงยังไม่กลายเป็นกระแสหลัก?
(tonybaloney.github.io)ทำไม Python Async ถึงยังไม่กลายเป็นกระแสหลัก?
asyncio ของ Python เป็นเครื่องมือทรงพลังที่สามารถลดเวลารอในสภาพแวดล้อมที่มีงาน I/O (อินพุต/เอาต์พุต) จำนวนมาก และช่วยเพิ่มประสิทธิภาพของโปรแกรมได้อย่างมาก แต่แม้จะมีข้อดีหลายอย่าง ก็ยังไม่ใช่นักพัฒนา Python ทุกคนที่จะนำมาใช้แบบจริงจัง ซึ่งมีเหตุผลเชิงพื้นฐานอยู่หลายข้อ
1. หัวเริ่มตีกัน: ภาระทางความคิด
อุปสรรคที่ใหญ่ที่สุดคือ ความซับซ้อน โค้ดแบบ synchronous นั้นเข้าใจได้ง่าย เพราะเราสามารถตามลำดับการทำงานจากบนลงล่างได้เหมือนอ่านหนังสือ
แต่โค้ดแบบ asynchronous ต่างออกไป ลองนึกภาพเชฟคนหนึ่งทำพาสต้า ระหว่างต้มเส้นอยู่ (ช่วงเวลาที่ต้องรอ) ก็หันไปทำซอส แล้วถ้ายังมีเวลาเหลือก็ไปหั่นผักต่อ ผลลัพธ์คืออาจทำอาหารเสร็จได้เร็วขึ้น แต่ในหัวของเชฟต้องคอยตามสถานะหลายอย่างตลอดเวลา เช่น ‘ตอนนี้เส้นสุกแค่ไหนแล้ว?’ หรือ ‘ซอสจะไหม้หรือเปล่า?’
เพราะลำดับการทำงานของโค้ดกระโดดไปมาระหว่างหลายจุด จึงทำให้ยากที่จะรู้ว่า ณ ตอนนี้งานไหนกำลังรันอยู่ และงานไหนกำลังรออยู่ โดยเฉพาะเวลาเกิดบั๊ก กระบวนการดีบักเพื่อไล่หาสาเหตุก็จะยิ่งยุ่งยากมาก
2. ระบบนิเวศแยกกันเดิน: ความเข้ากันได้ของไลบรารี
อีกปัญหาหนึ่งของ Python คือ ระบบนิเวศของไลบรารี ถูกแบ่งเป็นฝั่ง 'synchronous' และ 'asynchronous' ไลบรารียอดนิยมและใช้งานสะดวกจำนวนมาก (เช่น requests ซึ่งเป็นไลบรารีมาตรฐานสำหรับส่ง HTTP request หรือ ORM จำนวนมาก) ทำงานได้เฉพาะแบบ synchronous เท่านั้น
หากใช้ไลบรารี synchronous ผิดที่ผิดทางภายในโค้ด asynchronous ระหว่างที่โค้ด synchronous นั้นกำลังทำงาน ข้อได้เปรียบสำคัญที่สุดของ async อย่าง 'event loop' จะหยุดนิ่งไปทันที มันเหมือนสร้างถนนไว้หลายเลน แต่กลับใช้งานอยู่แค่เลนเดียว ทำให้แทบไม่มีประโยชน์ที่จะใช้ async เลย เพื่อแก้ปัญหานี้ นักพัฒนาต้องไปเรียนรู้และเปลี่ยนมาใช้ไลบรารีอีกชุดที่รองรับ async (aiohttp, asyncpg เป็นต้น) ซึ่งยิ่งทำให้เส้นโค้งการเรียนรู้ชันขึ้น
3. จัดการได้ทีละตัว: GIL (Global Interpreter Lock)
GIL (Global Interpreter Lock) เป็นหนึ่งในลักษณะเฉพาะที่เป็นปัญหาเรื้อรังของ Python โดยมันเป็นกลไกที่จำกัดว่า ภายในหนึ่งโปรเซส แม้จะมีหลายเธรด ก็สามารถมีได้เพียงหนึ่งเธรดเท่านั้นที่รันพร้อมกัน asyncio ทำงานอยู่บนเธรดเดียว จึงไม่ได้ชนกับ GIL โดยตรง แต่การมีอยู่ของ GIL ก็ยังจำกัดขอบเขตการใช้งานของ async อยู่ดี
asyncio เหมาะมากกับการใช้ประโยชน์จากช่วงเวลารอของ I/O (เช่น รอการตอบกลับจากเครือข่าย หรือรออ่านไฟล์) แต่ถ้ามี งานแบบ CPU-intensive ที่ต้องคำนวณหนักมากอยู่ภายในฟังก์ชัน async event loop ทั้งหมดจะหยุดค้างจนกว่าการคำนวณนั้นจะเสร็จ ระหว่างนั้นงาน I/O อื่น ๆ ก็ทำอะไรไม่ได้ นอกจากรอไปก่อน สุดท้ายแล้ว สำหรับงานที่ต้องการ parallel processing อย่างแท้จริง ก็ยังมีข้อจำกัดที่ต้องพึ่งเทคนิคอื่นอย่าง multiprocessing อยู่ดี
4. ความหวังในอนาคต: Python 3.14 และการถอด GIL
อย่างไรก็ตาม มีข่าวที่น่ามองในแง่ดีมากเกี่ยวกับข้อจำกัดเหล่านี้ นั่นคือความเคลื่อนไหวเรื่อง การถอด GIL แบบเลือกได้ ซึ่งเริ่มเข้ามาแบบทดลองตั้งแต่ Python 3.13 และคาดว่าจะมีความสมบูรณ์มากขึ้นในเวอร์ชัน 3.14
การเปลี่ยนแปลงนี้ถูกผลักดันผ่านข้อเสนอชื่อ PEP 703 โดยมีเป้าหมายให้ผู้พัฒนาสามารถรันโค้ด Python ได้โดยไม่ต้องมี GIL หากสิ่งนี้เกิดขึ้นจริง Python ก็จะสามารถทำ multithreading ที่แท้จริงได้ โดยหลายเธรดใช้หลาย CPU core พร้อมกัน
สิ่งนี้สามารถสร้างแรงเสริมอย่างมหาศาลเมื่อใช้ร่วมกับ asyncio งาน I/O จะถูกจัดการอย่างมีประสิทธิภาพด้วย asyncio ส่วนงาน CPU ที่ต้องคำนวณหนักก็สามารถโยนไปให้เธรดแยกไปประมวลผลแบบขนานได้ง่ายขึ้น โดยไม่ติดข้อจำกัดของ GIL การเปลี่ยนแปลงนี้มีแนวโน้มจะเป็นจุดเปลี่ยนสำคัญของระบบนิเวศ Python และมีความคาดหวังอย่างมากว่าจะช่วยทลายกำแพงหลายอย่างที่เคยขัดขวางการยอมรับ async ในวงกว้าง
ยังไม่มีความคิดเห็น