ชุมชนนักพัฒนา Go กำลังมีส่วนร่วมในการถกเถียงอย่างเข้มข้นเกี่ยวกับความซับซ้อนและความน่าเชื่อถือของ channel หลังจากมีบทความที่สำรวจแนวทางสามวิธีในการรักษาลำดับในแอปพลิเคชันที่ทำงานพร้อมกัน แม้ว่าโซลูชันทางเทคนิคที่นำเสนอจะให้วิธีการที่ซับซ้อนในการจัดการการประมวลผลที่เรียงลำดับ แต่การสนทนาได้เปิดเผยความกังวลที่ลึกซึ้งยิ่งขึ้นเกี่ยวกับ concurrency primitives ของ Go
สามแนวทางที่รักษาลำดับที่กล่าวถึง:
- Reply/In Channels: ใช้ช่องทางตอบกลับที่ไม่ซ้ำกันซึ่งแนบมากับแต่ละรายการข้อมูลนำเข้า โดยมี workers ทำงานพร้อมกันและมี dispatcher ประมวลผลผลลัพธ์ตามลำดับ
- sync.Cond "Zero-Buffering": รับประกันประสิทธิภาพหน่วยความจำสูงสุดโดยใช้ตัแปรเงื่อนไขในการประสานงานลำดับการทำงานของ goroutine
- Permission Passing Chain: สร้างห่วงโซ่ที่แต่ละ worker ต้องส่งผ่านสิทธิ์ให้กับ worker ถัดไปก่อนที่จะสามารถส่งออกผลลัพธ์ได้
ความสงสัยที่เพิ่มขึ้นเกี่ยวกับการใช้ Channel
ส่วนใหญ่ของชุมชนกำลังแสดงความผิดหวังต่อระบบ channel ที่ Go ภาคภูมิใจ นักพัฒนารายงานปัญหาร้ายแรงเกี่ยวกับบั๊ก deadlock ที่ยากต่อการ debug และการ crash ที่เกิดจากการปิด channel ไม่ถูกต้อง สิ่งนี้ทำให้โปรแกรมเมอร์ที่มีประสบการณ์หลายคนหลีกเลี่ยง channel และหันไปใช้แนวทาง mutex แบบดั้งเดิมแทน
ผมเริ่มเกลียด channel ใน Go มากขึ้นเรื่อยๆ มันเป็นแหล่งของบั๊ก deadlock ที่น่าสะพรึงกลัวและ debug ได้ยากมาก และการปิด channel ในจุดที่ผิดสามารถทำให้แอปทั้งหมดของคุณ crash ได้
การวิพากษ์วิจารณ์ขยายไปไกลกว่าประสบการณ์ส่วนบุคคล สมาชิกชุมชนสังเกตว่าโค้ดที่ใช้ channel สามตัวขึ้นไปมักจะมีข้อบกพร่องร้ายแรง โดยเฉพาะเมื่อต้องจัดการกับสถานการณ์ concurrency ที่ไม่ธรรมดา รูปแบบนี้กลายเป็นเรื่องธรรมดามากจนนักพัฒนาบางคนตรวจสอบโค้ดที่เกี่ยวข้องกับ channel หลายตัวล่วงหน้า
แนวทางทางเลือกและโซลูชันในโลกจริง
แทนที่จะต่อสู้กับรูปแบบ channel ที่ซับซ้อน นักพัฒนาหลายคนหันไปใช้โซลูชันภายนอก คิวงานที่รองรับด้วยฐานข้อมูล บริการคลาวด์อย่าง Google Cloud Tasks และระบบ message queue แบบดั้งเดิมกำลังได้รับความนิยมสำหรับการประมวลผลที่ต้องคำนึงถึงลำดับ แนวทางเหล่านี้มี durability ในตัวและการ debug ที่ง่ายกว่าเมื่อเทียบกับรูปแบบ concurrency ที่ซับซ้อนของ Go
นักพัฒนาบางคนกำลังใช้โซลูชันในหน่วยความจำที่ซับซ้อนโดยใช้โครงสร้างข้อมูลอย่าง min-heap สำหรับการประมวลผลที่เรียงลำดับจากแหล่งข้อมูลหลายแหล่ง เทคนิคเหล่านี้พิสูจน์ให้เห็นความมีประสิทธิภาพโดยเฉพาะสำหรับสถานการณ์อย่างการรวม log การประมวลผล time-series และการรวมไฟล์
ทางเลือกที่ชุมชนนักพัฒนาแนะนำ:
- ระบบคิวภายนอก: คิวงานที่รองรับด้วยฐานข้อมูล, Google Cloud Tasks , ระบบคิวข้อความ
- โครงสร้างข้อมูลในหน่วยความจำ: Min-heaps สำหรับการประมวลผลแบบเรียงลำดับจากหลายแหล่งข้อมูล
- การทำงานพร้อมกันแบบดั้งเดิม: วิธีการใช้ Mutex แทนการใช้ channels
- โซลูชันแบบผสม: ตัวนับแบบ Atomic ร่วมกับ min-heaps สำหรับสถานการณ์ที่ต้องการประสิทธิภาพสูง
ปัญหาการสร้าง Abstraction
การถกเถียงของชุมชนเผยให้เห็นความตึงเครียดพื้นฐานในปรัชญาการออกแบบของ Go การต่อต้าน abstraction และ generic ในอดีตของภาษานี้บังคับให้นักพัฒนาต้องสร้างโซลูชัน concurrency จากศูนย์ซ้ำๆ แม้ว่าการเพิ่ม generic เมื่อเร็วๆ นี้จะช่วยได้ แต่หนี้สินทางเทคนิคที่สะสมมาเป็นทศวรรษและนิสัยของชุมชนยังคงเป็นความท้าทายที่ต้องเอาชนะ
การถกเถียงเน้นให้เห็นว่าการตลาดของ Go เกี่ยวกับ concurrency ที่ปลอดภัยและง่ายด้วย channel อาจจะโฆษณาความเรียบง่ายของรูปแบบเหล่านี้เกินไป สิ่งที่ดูตรงไปตรงมาในบทเรียนกลับกลายเป็นเรื่องซับซ้อนและเสี่ยงต่อข้อผิดพลาดในสภาพแวดล้อมการผลิต ทำให้เกิดความชอบที่เพิ่มขึ้นต่อไลบรารีที่ผ่านการทดสอบแล้วมากกว่าโซลูชันที่สร้างขึ้นเอง
มองไปข้างหน้า
เมื่อระบบนิเวศของ Go เติบโตขึ้น มีการเปลี่ยนแปลงอย่างชัดเจนไปสู่แนวทาง concurrency ที่อนุรักษ์นิยมมากขึ้น นักพัฒนามอง channel มากขึ้นในฐานะเครื่องมือเฉพาะทางสำหรับผู้เขียนไลบรารีมากกว่าโครงสร้างการเขียนโปรแกรมในชีวิตประจำวัน วิวัฒนาการนี้แสดงให้เห็นว่าชุมชนกำลังเรียนรู้ที่จะสร้างสมดุลระหว่างความสามารถ concurrency ของ Go กับความกังวลด้านความน่าเชื่อถือในทางปฏิบัติ
การถกเถียงเน้นย้ำว่าแม้ว่าโมเดล concurrency ของ Go จะมีความสามารถที่ทรงพลัง แต่ความซับซ้อนของการรักษาลำดับในระบบ concurrent มักจะสนับสนุนทางเลือกที่เรียบง่ายและคาดเดาได้มากกว่า
อ้างอิง: Preserving Order in Concurrent Go Apps: Three Approaches Compared