1 คะแนน โดย GN⁺ 2023-07-30 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Python Steering Council มีแนวโน้มจะรับ PEP 703 เพื่อทำให้ GIL เป็นตัวเลือก ใน CPython และเสียงตอบรับจากชุมชนต่อทั้งข้อเสนอ no-GIL และ PEP 703 ก็ออกมาในเชิงบวกโดยรวม
  • เป้าหมายระยะยาวคือ รวมให้เหลือบิลด์ no-GIL แบบเดียว และต้องการหลีกเลี่ยงสถานการณ์ที่บิลด์ with-GIL กับ no-GIL รวมถึงอีโคซิสเต็มของ extension module แยกจากกันอย่างถาวร
  • ระหว่างการเปลี่ยนผ่าน ความเข้ากันได้ย้อนหลัง เป็นข้อจำกัดสำคัญ และแม้โค้ด third-party จะถูกแก้เพื่อรองรับ no-GIL ก็ยังต้องทำงานได้ต่อไปบนบิลด์ with-GIL
  • แผนคือการเปลี่ยนผ่าน 3 ระยะ ได้แก่ บิลด์ทดลองระยะสั้น บิลด์ที่รองรับระยะกลาง และบิลด์ค่าเริ่มต้นระยะยาว โดยบิลด์ทดลองอาจเข้า Python 3.13 ได้ แต่ถ้าเลื่อนไป 3.14 ก็ไม่ถือเป็นปัญหา
  • ก่อนจะสลับบิลด์ค่าเริ่มต้น ต้องยืนยันให้ได้ว่าชุมชนรองรับเพียงพอ และประสบการณ์ด้าน API, packaging และการแจกจ่ายพร้อมใช้งาน หากความสับสนมากกว่าประโยชน์ ก็ต้อง ยุติ PEP 703 และหาทางแก้อื่น

ทิศทางการรับ PEP 703 ของ Steering Council

  • Python Steering Council แสดงเจตนาว่าจะรับ PEP 703
  • แบบสำรวจเกี่ยวกับข้อเสนอ no-GIL ให้ผลตอบรับเป็นบวกโดยรวม และ Steering Council ก็มีมุมมองเชิงบวกต่อทั้งแนวคิดทั่วไปและ PEP 703
  • อย่างไรก็ตาม รายละเอียดของการรับข้อเสนอ ยังอยู่ระหว่างการจัดทำ และคาดว่าจะสรุปได้ภายในไม่กี่สัปดาห์ข้างหน้า

หลักการเปลี่ยนผ่านโดยไม่แยกสายถาวร

  • ในระยะยาว อาจใช้เวลามากกว่า 5 ปี เพื่อให้บิลด์ no-GIL กลายเป็นบิลด์เดียว
    • ไม่ต้องการให้เกิดสถานการณ์ที่บิลด์ with-GIL และ no-GIL แยกจากกันอย่างถาวร
    • และต้องการหลีกเลี่ยงโครงสร้างที่อีโคซิสเต็มของ extension module แยกถาวรตามสองบิลด์
  • ต้องจัดการเรื่องความเข้ากันได้ย้อนหลังอย่างระมัดระวังมาก
    • ไม่ต้องการสร้าง สถานการณ์แบบการเปลี่ยนผ่าน Python 3 ขึ้นมาอีก
    • แม้โค้ด third-party จะถูกแก้เพื่อรองรับบิลด์ no-GIL การเปลี่ยนแปลงนั้นก็ต้องทำงานได้บนบิลด์ with-GIL ด้วย
    • ปัญหาความเข้ากันได้ย้อนหลังกับ Python เวอร์ชันก่อนหน้าต้องจัดการแยกต่างหาก
    • นี่ไม่ใช่ Python 4
  • ข้อกำหนดด้านความเข้ากันได้ของ ABI, รายละเอียดของทั้งสองบิลด์ และผลกระทบต่อความเข้ากันได้ย้อนหลัง ยังอยู่ระหว่างการพิจารณา

เงื่อนไขที่ต้องตรวจสอบก่อนเปลี่ยนค่าเริ่มต้น

  • ก่อนเปลี่ยนให้บิลด์ no-GIL เป็นค่าเริ่มต้น ต้องตรวจสอบก่อนว่า การสนับสนุนจากชุมชน มีเพียงพอ
  • ไม่สามารถแค่เปลี่ยนค่าเริ่มต้นแล้วปล่อยให้ชุมชนไปหางานที่ต้องทำกันเองได้
  • นักพัฒนาแกนหลักต้องได้ทดลองใช้โหมดบิลด์ใหม่นี้และองค์ประกอบรอบข้างด้วยตนเอง
    • ระหว่างการจัดระเบียบความปลอดภัยของเธรดในโค้ดเดิม อาจจำเป็นต้องมี C API และ Python API ใหม่
    • และต้องแบ่งปันข้อค้นพบจากกระบวนการนั้นให้กับชุมชน Python
    • ต้องยืนยันให้ได้ว่าการเปลี่ยนแปลงที่นักพัฒนาแกนหลักต้องการและที่คาดหวังจากชุมชน อยู่ในระดับที่ยอมรับได้
  • ก่อนทำให้ no-GIL เป็นค่าเริ่มต้น ต้องสามารถเปลี่ยนทิศทางได้หากเห็นว่าการเปลี่ยนแปลงสร้างความสับสนมากเกินไปเมื่อเทียบกับประโยชน์
    • ในกรณีนั้น ต้องสามารถตัดสินใจย้อนงานทั้งหมดได้ด้วย
    • ดังนั้นโค้ดที่ใช้เฉพาะกับ no-GIL จึงควร สามารถระบุแยกได้ ในระดับหนึ่ง

แผนการเปลี่ยนผ่าน 3 ระยะ

  • ระยะสั้น จะเพิ่มบิลด์ no-GIL เป็น โหมดบิลด์เชิงทดลอง
    • อาจเข้า Python 3.13 ได้
    • และหากเลื่อนไป Python 3.14 ก็ไม่ถือเป็นปัญหา
    • สถานะเชิงทดลองมีไว้เพื่อให้ชัดเจนว่า นักพัฒนาแกนหลักสนับสนุนโหมดบิลด์นี้ แต่ไม่ได้หมายความว่าชุมชนจะพร้อมรองรับทันที
    • ยังต้องใช้เวลาเพื่อทำให้เกิดการสนับสนุนจากชุมชนในด้านการออกแบบ API, packaging และการแจกจ่าย
    • ไม่แนะนำให้ดิสทริบิวชันนำบิลด์ no-GIL แบบทดลองไปเป็นอินเทอร์พรีเตอร์ค่าเริ่มต้น
  • ระยะกลาง จะยกระดับบิลด์ no-GIL ให้เป็นสิ่งที่รองรับ แต่ยังไม่ตั้งเป็นค่าเริ่มต้น
    • ต้องมีความมั่นใจว่าการสนับสนุนจากชุมชนมากพอสำหรับการใช้งานในโปรดักชัน
    • ในระยะนี้จะกำหนดวันเป้าหมายหรือเวอร์ชัน Python สำหรับการทำให้ no-GIL เป็นค่าเริ่มต้น
    • ช่วงเวลาจะขึ้นอยู่มากกับความเข้ากันได้ย้อนหลังของการเปลี่ยน API, การจัดการ stable ABI และปริมาณงานที่ชุมชนเห็นว่าจำเป็น
    • อาจใช้เวลาอย่างน้อย 1~2 ปี หรือมากกว่านั้น
    • เมื่อประกาศว่าเป็นสิ่งที่รองรับแล้ว ผู้จัดทำดิสทริบิวชันบางรายอาจเริ่มให้ no-GIL เป็นค่าเริ่มต้น แต่ก็ขึ้นอยู่กับว่าขณะนั้นแพ็กเกจ Python รองรับ no-GIL มากน้อยเพียงใด
  • ระยะยาว ต้องการทำให้ no-GIL เป็นค่าเริ่มต้นและลบร่องรอยของ GIL ออกไป
    • จะไม่ทำลายความเข้ากันได้ย้อนหลังโดยไม่จำเป็น
    • หากต้องคงโหมดบิลด์หลักสองแบบไว้นานเกินไป ภาระของชุมชนอาจเพิ่มขึ้น เช่น ทรัพยากรการทดสอบและสถานการณ์การดีบักที่ต้องเพิ่มเป็นสองเท่า
    • แต่ก็ไม่สามารถเร่งรีบได้ และอาจใช้เวลาสูงสุด 5 ปี กว่าจะถึงระยะนี้

การประเมินซ้ำอย่างต่อเนื่องและความเป็นไปได้ในการยุติ

  • ตลอดกระบวนการนี้ ไม่ใช่แค่ Steering Council เท่านั้น แต่รวมถึง นักพัฒนาแกนหลัก ต้องประเมินความคืบหน้าและกรอบเวลาที่เสนออย่างต่อเนื่อง
  • ไม่ต้องการให้งานนี้กลายเป็น สงครามเรื่องความเข้ากันได้ย้อนหลังที่ยืดเยื้อ 10 ปี อีกครั้ง
  • หาก PEP 703 เริ่มส่งสัญญาณว่าเป็นปัญหา ต้องสามารถยุติและมองหาทางออกอื่นได้
  • ต้องตรวจสอบเป็นระยะว่างานที่ทำต่อเนื่องยังคุ้มค่าพอหรือไม่

1 ความคิดเห็น

 
GN⁺ 2023-07-30
ความคิดเห็นจาก Hacker News
  • ตลอดหลายสิบปีที่ผ่านมา โค้ดของ ไลบรารี C จำนวนมากมีคำเตือนในคู่มือว่าทำงานไม่เสถียรในบริบทแบบอะซิงโครนัส, reentrant และ recursive
    แต่เราก็เรียนรู้วิธีรับมือ และเวอร์ชันที่ปลอดภัยต่อการ reentry ก็ถูกทยอยปล่อยออกมาโดยไม่ทำให้ API ไม่เสถียรมากนัก
    มีทั้งการพาร์สสตริงแบบ tokenize แทนที่เดิมในที่เดิม, การเรียก DNS ที่ใช้บัฟเฟอร์แบบ static, โค้ดที่อาศัยพฤติกรรมสแตกเฉพาะของ Vax และผมมองว่า GIL เป็นทั้งพรและคำสาป

    • ยังจำได้ว่าต้องคุ้ยเอกสาร C runtime เพื่อหาฟังก์ชันที่ไม่ reentrant
      นั่นทำให้ติดนิสัยตรวจเอกสาร แม้จะเป็น API ที่พอรู้จักอยู่แล้ว เผื่อพลาดรายละเอียดสำคัญหรือมีอะไรเปลี่ยนไป
      ช่วงนั้นทำ C++ ข้ามแพลตฟอร์มอยู่ และ Java ที่ได้เห็นครั้งแรกมี concurrency, garbage collection และฟีเจอร์หลายอย่างที่เขียนง่ายกว่า C++ มาตั้งแต่ต้น เลยมั่นใจว่ามันจะดังมาก
      ต่อมานักพัฒนากระแสหลักเริ่มใช้ Python ซึ่งเท่าที่จำได้เดิมทีเป็นภาษาเสริมที่ฝังในโปรแกรมได้และเรียบง่าย ดังนั้น GIL จึงดูสมเหตุสมผลกว่าด้วย
    • โหมด No-GIL เป็นตัวเลือกเสริม และไลบรารีต่าง ๆ จะถูกทำเครื่องหมายว่า “รองรับ no-GIL” จากนั้น ecosystem ก็จะค่อย ๆ รองรับมากขึ้น
      ไม่ใช่ว่ามีใครกดสวิตช์แล้วทำให้โค้ด C น่าสงสัยจำนวนมหาศาลพังพร้อมกันในคราวเดียว
    • สภาพแบบนั้นคงอยู่ตลอดไปไม่ได้
      ตอนนี้มี CPU 128 คอร์ แล้ว และแม้แต่ CPU ราคาถูกก็มี 6 คอร์ ข้อจำกัดที่ผูกติดกับประสิทธิภาพคอร์เดียวจึงจะยิ่งใหญ่ขึ้นเรื่อย ๆ เมื่อเวลาผ่านไป
    • ตอนที่เริ่มจับ C อย่างจริงจังในยุค 90 ก็เคยเห็นคำเตือนแบบนี้
      ตอนแรกเชื่อว่าเป็นปัญหาที่จะแก้ได้ในไม่กี่สัปดาห์ หรืออย่างมากไม่กี่เดือน แต่ตอนนั้นผมรู้น้อยเกินไปจริง ๆ
    • ยากที่จะมองว่าเรื่องนี้เทียบเท่ากัน
      ใน C การโต้ตอบกับฟังก์ชันที่ไม่ thread-safe นั้นตรงไปตรงมามากกว่า และเวลาจะใช้ C คนส่วนใหญ่ก็ระมัดระวังกว่า
      ใน Python มีโมดูล C ทั้งก้อนที่มีสถานะส่วนกลาง พอโหลดมาสัก 10 ตัว แล้วบวกความซับซ้อนของอินเทอร์พรีเตอร์เข้าไป ในไม่ช้าก็ไม่มีใครรู้แล้วว่าเกิดอะไรขึ้น
      ทุกวันนี้นักพัฒนาส่วนใหญ่ แม้แต่ core developer เอง ก็ยังไม่ตรวจ memory leak ผมจึงไม่คิดว่าจะรัน tsan และต่อให้รันก็น่าจะเป็นชุดทดสอบเล็ก ๆ ที่ครอบคลุมโค้ดแค่ 10%
      เมื่อดูแนวปฏิบัติด้านการพัฒนาซอฟต์แวร์ของ Python โดยเฉพาะฝั่ง AI แล้ว ผมมองฟีเจอร์นี้ในแง่ลบมาก
  • น่าสนใจอยู่เหมือนกัน
    Python ประกอบขึ้นจาก ไลบรารีแชร์ของ C จำนวนมากที่เขียนขึ้นโดยคิดว่าสามารถพึ่งพา global lock ได้เป็นส่วนใหญ่
    บางตัวเรียบง่ายพอที่จะทำงานได้ดีแม้ไม่มี lock แต่บางตัวยังต้องมี lock และตอนนี้จะถูกกดดันให้รันโดยไม่มี GIL
    บางส่วนในนั้นคงจะลงมือทำ lock เองภายในขอบเขตของตัวเอง และบางทีสิ่งที่ Python ขาดจริง ๆ อาจเป็นการเรียก mutex แบบเฉพาะกิจที่กระจัดกระจายอยู่ทั่ว ecosystem ก็ได้
    ไม่เคยคิดเลยว่า Python จะพังในรูปแบบที่นำ data race และ deadlock เข้ามาโดยอ้างเรื่องประสิทธิภาพ
    การทำให้ไลบรารี C ที่เขียนโดยสมมติว่ามี global lock กลายเป็น thread-safe เป็นงานประเภทที่แม้แต่ผู้เชี่ยวชาญด้าน concurrency ก็ยังห้าม และเป็นงานที่พลาดได้ง่ายระหว่าง implement
    สมมติฐานของผมคือ คนส่วนใหญ่ที่เขียน Python C extension ไม่ใช่ผู้เชี่ยวชาญด้าน concurrency แต่เป็นโปรแกรมเมอร์เก่ง ๆ ที่ไม่หนีความท้าทาย และเมื่อรวมกันแบบนี้ data race/อาการค้าง/segfault แทบจะหลีกเลี่ยงไม่ได้

    • การทำให้เป็น opt-out แบบชัดเจนในขั้นแรกดูโอเค
      แต่ความคาดหวังว่าจะเปลี่ยนเป็น opt-in ในอีก 5 ปีข้างหน้านั้นมองโลกในแง่ดีเกินไป
      นักพัฒนาไลบรารีทุกคนต้องแก้ทั้งไลบรารีของตัวเองและไลบรารี Python ด้วย เป็นงานหนัก และต่อให้ทำได้ดีก็แทบไม่มีใครสังเกตเห็น จึงยากจะได้รับผลตอบแทน
      มีไลบรารีจำนวนมากที่ไม่เคยมีกรณีใช้งานแบบ multiprocessing เลย และไลบรารีขนาดใหญ่ก็หลีกเลี่ยงไม่ได้ที่จะเกิดบั๊กละเอียดอ่อน ผลลัพธ์ที่ดูเหมือนรับประกันได้คือคำบ่นที่ reproduce ไม่ได้และนักพัฒนายอมแพ้
    • ไลบรารี C ส่วนใหญ่ที่ถูกเรียกใช้บ่อยใน Python ก็มักมี binding สำหรับภาษาอื่นด้วย ดังนั้นถึงตอนนี้ก็น่าจะ thread-safe แล้วไม่ใช่หรือ
      Python ที่มี GIL ก็ยังรองรับ thread ดังนั้นไลบรารีเหล่านี้อย่างน้อยก็น่าจะปลอดภัยต่อ reentry
      ถ้าเป็นไลบรารีที่ reentrant ได้แต่ไม่ thread-safe การเพิ่ม global lock หนึ่งตัวเพื่อครอบทุกการเรียกก็น่าจะเพียงพอ และนั่นค่อนข้างคล้ายกับสิ่งที่ GIL เคยทำ
      การทำให้ไลบรารีเดิมทำงานได้โดยไม่มี GIL ในหลายกรณีน่าจะค่อนข้างตรงไปตรงมา แม้ parallelism อาจต้องถูกสละไป
      ปัญหาหลักน่าจะอยู่ที่ไลบรารีฝั่ง C ที่ callback กลับเข้า Python runtime
  • เป็นคำถามแบบซื่อ ๆ แต่มีแพ็กเกจ asyncio กับ multiprocessing อยู่แล้ว เลยสงสัยว่าใครจำเป็นต้องใช้ No-GIL กัน
    ผมไม่เคยเจอปัญหาใน Python เพราะ GIL เลย และมักเลี่ยงได้เสมอด้วยการเปิด ThreadPool หรือ ProcessPool หรือใช้ไลบรารีแบบ async เมื่อจำเป็น
    เลยอยากรู้ว่ามีกรณีใช้งาน No-GIL แบบไหนที่ multiprocessing แก้ไม่ได้บ้าง
    ผมเคยคิดว่าการรันแบบเธรดเดียวโดยไม่มีโอเวอร์เฮดของ primitive ด้าน concurrency นั้นดีที่สุดสำหรับงานคอมพิวติ้งสมรรถนะสูง เหมือนที่ LMAX Disruptor แสดงให้เห็น

    • ประเด็นหลักมีแค่เรื่องประสิทธิภาพ
      asyncio โดยพื้นฐานเป็นเธรดเดียว จึงใช้ได้คอร์เดียว ส่วน multiprocessing ใช้หลายคอร์จึงดีกว่าในแง่ประสิทธิภาพ แต่แต่ละโปรเซสค่อนข้างหนัก และมีโอเวอร์เฮดของหน่วยความจำที่ใช้ร่วมกันเพิ่มเข้ามา
      มัลติเธรดที่อิง GIL เป็นคอร์เดียว แถมเขียนให้ถูกต้องก็ยาก
      มัลติเธรดแบบ No-GIL ใช้หลายคอร์ แต่ใช้งานยาก และแม้ไม่แน่ใจเรื่องการทำงานภายใน แต่หน่วยความจำที่แชร์กันควรเร็วกว่าการใช้ multiprocessing
      ถ้าจะออกแบบระบบใหม่ ผมเห็นด้วยว่าในกรณีใช้งาน Python แทบทั้งหมดควรเลี่ยงเธรด แล้วใช้ asyncio/multiprocessing แทน
      โปรแกรม Python ที่ต้องการมัลติเธรดเร็ว ๆ มักเป็นกรณีที่ไม่ควรเขียนด้วย Python ตั้งแต่แรก แต่ในทางปฏิบัติมีคนที่เขียนโค้ด CPU-intensive ด้วย Python ไปแล้ว ดังนั้น No-GIL จึงมีประโยชน์เชิงปฏิบัติ
    • มีหลายกรณีที่ No-GIL แก้ได้แต่ multiprocessing แก้ไม่ได้
      สมมติว่ามีเว็บเซิร์ฟเวอร์ที่ใช้สถานะร่วมกันเพื่อตอบสนองไคลเอนต์หลายรายพร้อมกัน multiprocessing ใช้ pickle ในการส่งข้อมูลไปมา จึงมีโอเวอร์เฮดด้านประสิทธิภาพสูง
      เช่น ถ้าอยากเก็บ โครงสร้างข้อมูล 1GB ไว้ในหน่วยความจำแล้วคำนวณแบบขนาน multiprocessing จัดการให้ได้ประสิทธิภาพดีได้ยาก
      การใช้ pickle ทำให้แชร์อ็อบเจ็กต์ทั้งหมดไม่ได้ และข้อผิดพลาดว่าอ็อบเจ็กต์ pickle ไม่ได้ก็ debug ยากมากในโครงสร้างข้อมูลที่ซับซ้อน
      โดยเฉพาะอ็อบเจ็กต์ที่สร้างโดยไลบรารีเนทีฟ อาจแชร์ไม่ได้
      งานที่ต้องแชร์สถานะระหว่างรันก็ทำด้วยโมดูล multiprocessing ได้ยากมาก และแม้แต่ Prometheus exporter สำหรับ Flask หากต้องรวมสถิติจากทุกโปรเซส ก็ยังต้องใช้แฮ็กแปลก ๆ อย่างไดเรกทอรีชั่วคราว
    • ใน PEP 703 Manuel Kroiss จากทีม reinforcement learning ของ DeepMind อธิบายว่าเพราะ คอขวด GIL ทำให้ต้องเขียนโค้ดเบส Python ใหม่เป็น C++ และทำให้นักวิจัยเข้าถึงได้ยากขึ้น
      เขาบอกว่าแอปพลิเคชันจำนวนมากของ DeepMind อยากรันเธรดประมาณ 50–100 เธรดต่อโปรเซส แต่บ่อยครั้ง GIL ก็กลายเป็นคอขวดตั้งแต่ยังไม่ถึง 10 เธรด
      ทางเลี่ยงคือใช้ subprocess ในบางกรณี แต่หลายครั้งโอเวอร์เฮดของการสื่อสารข้ามโปรเซสสูงเกินไป สุดท้ายจึงต้องย้ายส่วนใหญ่ของโค้ดเบส Python ไปเป็น C++
      สำหรับการใช้งานทั่วไปอย่างเว็บแอป multiprocessing อาจเพียงพอ แต่ในเวิร์กโหลด AI ขนาดใหญ่อย่างของ Google และ DeepMind นั้น GIL จำกัดการใช้ Python จริง ๆ
      นี่ก็เป็นเหตุผลที่ Meta ตั้งใจจะทุ่มแรงวิศวกรเทียบเท่า 3 ปีให้กับงานนี้: https://news.ycombinator.com/item?id=36643670
    • สำหรับปัญหาแบบ CPU-bound asyncio ใช้ไม่ได้เลย เพราะ event loop ยังรันได้แค่บนคอร์เดียว
      ตามชื่อแล้ว มันช่วยได้จริง ๆ เฉพาะปัญหาแบบ I/O-bound เท่านั้น
      การแชร์ข้อมูลระหว่างหลายโปรเซสนั้นทรมานมาก และการทำ data control ควบคู่กับ process orchestration ก็ยิ่งทรมานกว่า
      โปรเซสมีต้นทุนสูง และด้วยความยากในการแชร์ข้อมูลที่กล่าวไปข้างต้น greenlet ก็ยากจะเป็นทางเลือกจริง ๆ
    • สำหรับเว็บแอปพลิเคชันทั่วไป คงไม่ได้ปฏิวัติอะไรมากนัก
      แต่ใน领域อย่าง AI และ data science ที่ Python มีบทบาทสูง การสามารถเปิดเธรด CPU/GPU-bound จำนวนมากมารันได้ถือเป็นข้อได้เปรียบใหญ่
  • C extension จำนวนมากไม่ได้เขียนโดยคำนึงถึงมัลติเธรด จึงอาจเกิดปัญหาได้ และผมคิดว่าน่าจะเกิดจริง
    มีตัวอย่างเล็ก ๆ ที่ไม่ปลอดภัยหาก lst ถูกเข้าถึงจากเธรดอื่นอยู่ที่นี่: https://news.ycombinator.com/item?id=36649769
    แม้ตอนนี้ หากโค้ด C callback กลับเข้าไปยัง Python bytecode ผ่านเมธอด __del__ และ bytecode นั้นยาวพอ อาจเกิด context switch ได้ คงประมาณ 100 คำสั่ง
    แต่ก็เป็นกรณีที่พบได้น้อยมาก และโค้ด C extension จำนวนมากไม่ได้เขียนโดยคำนึงถึงสถานการณ์แบบนี้
    คนที่ใช้ C extension อาจพึ่งพาว่ามันจะถูกรันแบบ atomic อยู่ก็ได้
    เช่น วิธีใส่และรับ numpy array ผ่าน thread pool ตอนนี้ทำงานได้ดี แต่ถ้าไม่มี GIL อาจพังได้

    • ปัญหาแบบนั้นจะถูกพบเยอะมากแน่ ๆ และน่าเสียดายที่มันจะปรากฏเป็น race condition ที่หาและ debug ได้ยาก
      เพราะอย่างนั้น ข้อเสนอและทิศทางการทำงานจึงเป็นการทำให้โหมด non-GIL เป็นตัวเลือกโดยสมบูรณ์ และไม่ตั้งเป็นค่าเริ่มต้น
      คนส่วนน้อยที่กล้าเปิดใช้ต้องเตรียมใจว่าจะใช้เวลามหาศาลไปกับการหาและแก้ race condition ละเอียดอ่อนในโค้ดไลบรารี Python ที่มีอายุหลายสิบปี
      ผู้ใช้กลุ่มแรกจะเจ็บตัวมาก หรือที่เป็นไปได้มากกว่าคือจะจำกัดการใช้ non-GIL ไว้เฉพาะโปรเซสเฉพาะทางมาก ๆ ที่ลด dependency ให้มากที่สุด
    • ผมมองว่าโอเค
      มันคือการเปลี่ยนจากสภาพที่สงสัยว่าจะมีปัญหา ไปสู่สภาพที่รู้ชัดว่าปัญหาอยู่ตรงไหน แล้วที่เหลือก็ค่อย ๆ ลดรายการนั้นลงทีละข้อ
      อาจเพิ่ม mutex บางรูปแบบรอบ ๆ โค้ด หรือเปลี่ยนไปใช้ implementation ทางเลือกที่ไม่ใช่ native code ซึ่งมีโอกาสเกิดปัญหาน้อยกว่า
      เหตุผลคัดค้านส่วนใหญ่ดูเหมือนจะเป็นว่า “งานเยอะ” ไม่ใช่ว่าเป็นไปไม่ได้
      ถ้ามีคนทำมากพอ ก็อาจได้ผลลัพธ์ออกมา
    • คำว่า “พบได้น้อยมาก” สำหรับคนที่ทำงานกับ runtime แบบขนาน/async เพื่อรองรับเซิร์ฟเวอร์ที่รันต่อเนื่องหลายพันเครื่อง หมายความว่าในความเป็นจริงมันพังอยู่เสมอ แต่ debug ไม่ได้
    • ผมคิดว่า core developer ของ CPython รู้เรื่องปัญหาเหล่านี้ดีมาก
      ถ้าไม่ใช่แบบนั้น พวกเขาคงประกาศแผนถอด GIL ออกทั้งก้อนแบบฉับพลัน แทนที่จะใช้แนวทางเป็นขั้น ๆ ที่ให้ผู้ใช้เลือก โหมด No-GIL เอง
    • นี่อาจเป็นจังหวะที่ดีในการทำ C extension ที่ให้ความสำคัญกับ concurrency เป็นหลัก เพื่อให้แข่งขันกับ extension เดิมได้
  • ลองนึกถึงการเปลี่ยนจากข้อความไปเป็น Unicode, จาก 32 บิตเป็น 64 บิต, จาก Intel ไปเป็น ARM และ Y2K
    No-GIL เป็นการเปลี่ยนแปลงที่เล็กกว่ามาก และสามารถเดินตามเส้นทางการเปลี่ยนผ่านแบบเดียวกันได้โดยไม่ทำลายอะไรแบบรุนแรง
    ต่อให้มีอะไรพัง ก็จะมีวิธีที่นิยามไว้อย่างชัดเจนสำหรับจัดการกรณีเหล่านั้น
    ไม่ว่าอย่างไรเราก็ผ่านการเปลี่ยนผ่านเหล่านั้นมาได้ และดีใจที่ได้เห็นการเดินหน้าต่อไป
    มันจะเปิดพื้นที่อีกมากที่จนถึงตอนนี้ถูกระบุว่าเป็นไปไม่ได้
    สิ่งหนึ่งที่ Swift ช่วงแรกทำได้ดีคือการวาง breaking changes ไว้ภายใต้คำมั่นที่ชัดเจน และทุกคนก็รู้ตำแหน่งของตัวเองและปรับตัวได้ดี
    บางครั้งก็อยากให้ Python เลือกเส้นทางเดียวกัน

    • อันนั้นต่างกันนิดหน่อย
      การเปลี่ยนจาก 32 บิตเป็น 64 บิต, ไปเป็น ARM, และ Y2K ล้วนทดสอบได้ว่าทำงานหรือไม่
      แน่นอนว่าการทดสอบอาจไม่ครอบคลุมกรณีที่ล้มเหลว แต่การทดสอบที่ทำไปแล้วให้ผลแบบกำหนดแน่นอน
      แต่ในกรณีนี้ ไม่ว่าจะทดสอบเท่าไร คำตอบก็คือ “ถูกต้องแล้ว หรือแค่ยังไม่ไปแตะ race condition ที่เหมาะสมเท่านั้น”
    • ไม่ได้กังวลเรื่องความยากของการไมเกรต แต่กังวลว่าสถานะสุดท้ายอาจแย่กว่าตอนนี้จริง ๆ
    • การย้ายจากข้อความไปเป็น Unicode เป็นปัญหาใหญ่เป็นพิเศษสำหรับ Python
  • แม้จะระบุชัดว่าไม่อยากให้ซ้ำรอยสถานการณ์การเปลี่ยนผ่านของ Python 3 แต่แนวทางตอนนี้ก็ดูน่าขนลุกว่าคล้ายเส้นทางนั้นมาก
    หลายอย่างขึ้นอยู่กับชุมชน Python และช่องทางการแจกจ่าย
    ชุมชนอาจนำไปใช้ไม่ทันเวลา หรือดิสโทรอย่าง Ubuntu, Fedora, Anaconda อาจรีบเดินหน้าเร็วเกินไป
    ยังเร็วเกินไปที่จะฟันธง แต่สงสัยว่า Steering Council มีอำนาจควบคุมจริงมากแค่ไหนเพื่อหลีกเลี่ยงสถานการณ์แบบนี้

    • มีการบอกว่า 5 ปีหลังจากที่มี No-GIL ให้ใช้ อยากให้มันกลายเป็นโหมดบิลด์เพียงโหมดเดียว ซึ่งทั้งยาวเกินไปและสั้นเกินไป
      5 ปียาวเกินไปสำหรับการที่ Python จะมี สองโหมด
      ครึ่งทศวรรษนานพอให้สองโหมดกลายเป็นสภาพเดิมที่ฝังตัว และหลังจากนั้นโพสต์ Stack Overflow เก่า ๆ ก็จะยังคงอยู่
      ไม่ได้มองโลกในแง่ดีว่า 5 ปีจะไม่ยืดเป็น 10 ปีของความไม่แน่นอนและการพังเสียหาย
      ในทางกลับกัน 5 ปีก็อาจสั้นเกินไปสำหรับการขุด C code ทั้งหมดออกมาแก้ ทดสอบ และเรียกว่าสุกงอมแล้ว
    • มันจะคล้ายกับสถานการณ์ 2to3
      บริษัทที่สัญญาว่าจะสนับสนุนอาจแปลงบางโปรเจกต์แบบกลไก แล้วไปรบกวนนักพัฒนาตัวจริงหรือขู่ด้วยการ fork
      บั๊กจะถูกขัดเกลาโดยนักพัฒนาตัวจริงที่ไม่ได้รับค่าจ้างตลอดหลายปี
      แต่ Python ต้องการ “ความสำเร็จ” บางอย่าง และนี่ก็เป็นข้อความประชาสัมพันธ์ที่ดี
      ในโลกของ Python ดูเหมือนว่าความถูกต้องไม่ได้สำคัญมากนัก
    • ในการเปลี่ยน 2-to-3 ได้ใช้ไพ่การเปลี่ยน major version ที่ทำให้ของพังโดยไม่มีเหตุผลมากนักไปแล้ว และตอนนี้เมื่อกำลังเผชิญการเปลี่ยนแปลงใหญ่ก็พูดว่า “จะไม่เป็นเหมือน 2-to-3”
      เพราะประวัตินั้น จึงฟังดูเหมือนพยายามคงสองโหมดการรันไว้ภายใน CPython 3 แทนที่จะเดินหน้าไปสู่การเปลี่ยน major อีกครั้ง
  • โชคดีที่ดูตระหนักมากว่านี่อาจกลายเป็น มหันตภัย Python 4 ได้ง่าย ๆ
    ต้องระมัดระวังอย่างยิ่งไม่ให้กระทบพฤติกรรมแบบ yes-GIL โดยไม่ตั้งใจ
    ถ้า GIL ที่จำลองขึ้นมาในรูปแบบใดรูปแบบหนึ่งไม่เหมือน GIL จริงอย่างแม่นยำ ก็จะเกิดกรณีประหลาดสารพัดได้

    • เหตุผลที่เห็นมาจนถึงตอนนี้ว่าทำไมมันจะต่างจาก 2 -> 3 มีแค่สองข้อ
      ข้อแรก ไม่อยากให้เป็นแบบนั้น
      ข้อสอง ถ้าเป็นแบบนั้นจะรีบถอย
      ทั้งสองข้อสำคัญ แต่ดูเหมือนยังขาดชิ้นที่สามซึ่งสำคัญคือ “และเราจะทำให้สำเร็จด้วยวิธีนี้”
    • เชื่อว่าเจตนาดี แต่ไม่มั่นใจว่าจะหลีกเลี่ยงได้หรือไม่
      ตอนนี้ก็พูดแล้วว่า GIL กับ No-GIL อาจอยู่ร่วมกันได้นานกว่า 5 ปี
      สำหรับผู้สร้างเครื่องมือ นั่นหมายถึงต้นทุนจะเพิ่มเป็นสองเท่าอย่างน้อยในอีก 5 ปีข้างหน้า
      เพราะไม่ว่าจะเพื่อการใช้งานจริงหรือการทดลอง ผู้คนก็อยากใช้เครื่องมือในทั้งสองโหมด
  • GIL คือ Global Interpreter Lock
    มีคำอธิบายดี ๆ อยู่ที่นี่: https://realpython.com/python-gil/

  • เรื่องนี้มีปัญหาใหญ่สองข้อ
    ข้อแรก การปรับปรุงบางอย่างคุ้มค่าที่จะทำลายความเข้ากันได้ย้อนหลัง และ การนำ GIL ออก ก็เป็นการปรับปรุงแบบนั้น
    การเปลี่ยนแปลงของ Python 3 คุ้มค่าหรือไม่นั้นยังถกเถียงกันได้ และการที่ print กลายเป็นฟังก์ชันก็ดูไม่ได้คุ้มค่าเป็นพิเศษ
    อย่างไรก็ตาม การเปลี่ยนผ่าน 2-to-3 มีส่วนที่ถูกชนกลุ่มน้อยเสียงดังพูดเกินจริง และจากประสบการณ์ที่เคยย้ายโค้ดเบสมากกว่า 5 ชุดจาก 2 ไป 3 ส่วนใหญ่มีปัญหาน้อย
    ปัญหาใหญ่ที่สุดคือโค้ดเบสที่นักพัฒนาก่อนหน้าเอาไลบรารีเข้ามาใช้กับทุกอย่างจนกลายเป็นกองไลบรารีที่ถูกทิ้งร้าง และโค้ดแบบนี้ก็จะเจอปัญหาอยู่ดีแม้ภาษาแกนกลางจะไม่ทำลายความเข้ากันได้
    คำตอบไม่ใช่การผลักดันให้ภาษาห้ามทำลายความเข้ากันได้ไปตลอดกาล แต่คืออย่าคาดหวังว่าการดึง pip ทั้งหมดเข้ามาจะเป็นกลยุทธ์ที่ยั่งยืน
    ตอนนี้ Steering Council ถูกชนกลุ่มน้อยเสียงดังตำหนิมากเกินไปจนกลัวการเปลี่ยนแปลงที่ทำให้ของพัง
    แต่การนำ GIL ออกเป็นส่วนที่พื้นฐานเกินไปของวิธีการทำงานของ Python จึงควรเป็น breaking change และควรยอมรับเรื่องนี้แล้ววางแผนการเปลี่ยนผ่าน ดีกว่าพยายามทำสิ่งที่เป็นไปไม่ได้คือทำให้ไม่พังทั้งที่ไม่กล้ายอมรับข้อเท็จจริงเพราะกลัวผู้ใช้
    แม้แต่ใน Python 3.11 โค้ดเบสของผมก็พัง และการแก้ไขไม่ได้ยาก แต่หวังว่าจะมีการสื่อสารที่ดีกว่านี้ว่าสิ่งแบบนี้อาจเกิดขึ้นได้
    ข้อสอง ปัญหาที่พื้นฐานกว่านั้นคือฟีเจอร์อื่น ๆ จำนวนมากของ Python ถูกสร้างขึ้นโดยมี GIL เป็นศูนย์กลาง
    โดยเฉพาะ พาราไดม์แบบ asynchronous มีความหมายมากเพราะ GIL แต่ถ้าไม่มี GIL เมื่อมองย้อนกลับไป โมเดล actor แบบ send/recv สไตล์ Erlang น่าจะเป็นทิศทางที่ดีกว่ามาก
    การย้อนกลับเรื่องนี้ทำได้ยาก และรู้สึกเหมือน Python กำลังถูกผลักไปเป็นชุดฟีเจอร์ที่เกาะกันไม่แน่นและไม่ค่อยเข้ากัน จึงดูเหมือนทำน้อยเกินไปและสายเกินไป

  • ขอขอบคุณนักพัฒนาหลักของ Python และ Steering Council และ Python ก็เป็นหนึ่งในภาษาที่ชอบควบคู่ไปกับ Java และ C
    ยินดีอย่างยิ่งกับ การทำงานแบบมัลติเธรดจริงๆ ของ Python
    ผมใช้ทั้ง multiprocessing และ multithreading แล้วแต่โปรเจกต์ โดยมีตัวอย่าง multiprocessing อยู่ที่ [0] และตัวอย่างการใช้ Python Threads กับงานที่มี I/O จำนวนมากอยู่ที่ [1]
    แต่การใช้เธรดจริงน่าจะมีประสิทธิภาพกว่ามาก
    เธรดสามารถส่งข้อมูลปริมาณเท่าใดก็ได้ไปมาในงานแบบอะตอมมิกเดียวที่แทบจะเกิดขึ้นทันที แต่ทำแบบนั้นไม่ได้ด้วยอินเทอร์เฟซลูปแบ็กในเครื่อง หรือ multiprocessing หรือ pipe
    กำลังทำงานกับสถาปัตยกรรมมัลติเธรดที่เรียกว่า “three tier multithreading architecture”
    https://github.com/samsquire/three-tier-multithreaded-archit...
    เป้าหมายคือเซิร์ฟเวอร์ที่ขยายขนาดได้อย่างสุดขีดและมีประสิทธิภาพสูง แต่ Python อาจไม่ใช่เครื่องมือที่เหมาะกับงานนั้นก็ได้
    [0]: https://news.ycombinator.com/item?id=36897054 คำอธิบายการใช้ multiprocessing
    [1]: https://devops-pipeline.com/ ตัวอย่างการใช้ multithreading