ชุมชนโปรแกรมเมอร์กำลังมีการถกเถียงกันอย่างเข้มข้นเกี่ยวกับว่าภาษา Go สามารถถือได้ว่าเป็นภาษาที่มีความปลอดภัยของหน่วยความจำจริงหรือไม่ การอภิปรายนี้มีจุดศูนย์กลางอยู่ที่คำถามพื้นฐาน: ภาษาหนึ่งสามารถอ้างว่ามีความปลอดภัยของหน่วยความจำได้หรือไม่ เมื่อการเขียนโปรแกรมแบบ concurrent สามารถนำไปสู่ undefined behavior และช่องโหว่ด้านความปลอดภัยที่อาจเกิดขึ้นได้
ความขัดแย้งนี้เกิดจากแนวทางของ Go ในการจัดการกับโครงสร้างข้อมูลแบบ multi-word เช่น interfaces และ slices ในสภาพแวดล้อมแบบ concurrent ซึ่งแตกต่างจากภาษาอื่นๆ เช่น Java หรือ C# ที่ลงทุนอย่างหนักในการรับประกันว่าแม้แต่โปรแกรมที่มี data races ยังคงมีการกำหนดไว้อย่างชัดเจน Go กลับอนุญาตให้เงื่อนไข race บางอย่างทำลายการรับประกันความปลอดภัยพื้นฐานของภาษา
ปัญหาทางเทคนิค
เมื่อ goroutines หลายตัวเข้าถึง interface types หรือ slices ของ Go พร้อมกันโดยไม่มีการซิงโครไนซ์ที่เหมาะสม runtime อาจประสบกับ torn reads ซึ่งเป็นสถานการณ์ที่ thread อ่านข้อมูลที่ถูกอัปเดตเพียงบางส่วน สิ่งนี้เกิดขึ้นเพราะโครงสร้างข้อมูลเหล่านี้ถูกนำมาใช้เป็นค่า multi-word ที่ไม่สามารถอัปเดตแบบ atomic บนโปรเซสเซอร์ส่วนใหญ่
ปัญหานี้จะปรากฏเมื่อ goroutine หนึ่งอัปเดตตัวแปร interface ในขณะที่อีกตัวหนึ่งกำลังอ่านมัน goroutine ที่อ่านอาจสังเกตเห็นข้อมูลเก่าและใหม่ปนกัน ซึ่งอาจทำให้ถือว่าค่า integer เป็น memory pointer สิ่งนี้สามารถนำไปสู่ segmentation faults เมื่อโปรแกรมพยายาม dereference memory addresses ที่ไม่ถูกต้อง
โครงสร้างข้อมูลที่มีปัญหาของ Go
- Interface types: เก็บข้อมูลในรูปแบบคู่ pointer (ข้อมูล + vtable) ที่ไม่สามารถอัปเดตแบบ atomic ได้
- Slices: โครงสร้างแบบ multi-word ที่ประกอบด้วย data pointer, length และ capacity
- Maps: ออกแบบมาให้ไม่ปลอดภัยสำหรับ thread อาจทำให้เกิดการเสียหายของข้อมูลเมื่อมีการเข้าถึงพร้อมกัน
- Channels: ปลอดภัยสำหรับกรณีการใช้งานที่ออกแบบมา แต่ไม่สามารถป้องกันการแชร์ข้อมูลที่ไม่ปลอดภัยได้
การตอบสนองของชุมชนและผลกระทบต่ออุตสาหกรรม
ชุมชนโปรแกรมเมอร์มีปฏิกิริยาที่หลากหลายต่อการค้นพบเหล่านี้ นักพัฒนาบางคนโต้แย้งว่าสิ่งนี้แสดงถึงช่องว่างที่สำคัญระหว่างความปลอดภัยที่ Go โฆษณาและการรับประกันที่แท้จริง คนอื่นๆ โต้แย้งว่าสถานการณ์เช่นนี้หายากในทางปฏิบัติ และ race detector ในตัวของ Go ช่วยระบุปัญหาเหล่านี้ระหว่างการพัฒนา
ทุกครั้งที่มีการสนทนาเรื่องนี้ ฉันนึกถึงทีมของฉันที่ Dropbox ที่การแนะนำ segfault ใน Go server ของเราโดยไม่ซิงโครไนซ์การเขียนไปยังโครงสร้างข้อมูลเป็นพิธีกรรมสำหรับวิศวกรใหม่
การถกเถียงนี้มีผลกระทบในวงกว้างต่อวิธีที่อุตสาหกรรมจัดหมวดหมู่ภาษาโปรแกรม องค์กรเช่น NSA และหน่วยงานรัฐบาลต่างๆ ได้รวม Go ไว้ในรายชื่อภาษาที่มีความปลอดภัยของหน่วยความจำ ซึ่งเป็นคำแนะนำที่อาจต้องพิจารณาใหม่เนื่องจากข้อจำกัดทางเทคนิคเหล่านี้
ภูมิทัศน์ความปลอดภัยของหน่วยความจำในวงกว้าง
การอภิปรายนี้เน้นให้เห็นความไม่เห็นด้วยพื้นฐานเกี่ยวกับคำศัพท์ในโลกการเขียนโปรแกรม ผู้เชี่ยวชาญด้านความปลอดภัยมักจะกำหนดความปลอดภัยของหน่วยความจำตามศักยภาพในการใช้ประโยชน์ในทางปฏิบัติ ในขณะที่นักทฤษฎีภาษาโปรแกรมมุ่งเน้นไปที่การรับประกันอย่างเป็นทางการและการป้องกัน undefined behavior
ภาษาเช่น Rust และ Swift ใช้แนวทางที่แตกต่างกัน โดยใช้ระบบ type ที่ซับซ้อนเพื่อป้องกัน data races ทั้งหมด Java และ C# ลงทุนใน memory models ที่รับประกันว่าแม้แต่โปรแกรมที่มี race ยังคงมีการกำหนดไว้อย่างชัดเจน แม้ว่าอาจไม่ถูกต้องในตรรกะของมัน
การเปรียบเทียบความปลอดภัยของหน่วยความจำตามภาษาโปรแกรม
ภาษา | โมเดลหน่วยความจำ | การจัดการ Data Race | ความปลอดภัยอย่างเป็นทางการ |
---|---|---|---|
Go | พื้นฐาน | อาจทำให้เกิด UB | จำกัด |
Java | JMM (Java Memory Model) | พฤติกรรมที่กำหนดไว้อย่างชัดเจน | แข็งแกร่ง |
C | CLR Memory Model | พฤติกรรมที่กำหนดไว้อย่างชัดเจน | แข็งแกร่ง |
Rust | Ownership + Send/Sync | ป้องกันในขณะ Compile | แข็งแกร่ง |
Swift | Strict Concurrency | ป้องกันในขณะ Compile | แข็งแกร่ง |
JavaScript | Single-threaded + Web Workers | การส่งผ่านข้อความ | แข็งแกร่ง |
บทสรุป
แม้ว่า Go จะยังคงปลอดภัยกว่าภาษาเช่น C หรือ C++ อย่างมาก หลักฐานชี้ให้เห็นว่ามันไม่สามารถให้การรับประกันความปลอดภัยของหน่วยความจำในระดับเดียวกับภาษาที่ปลอดภัยอย่างแท้จริง สำหรับแอปพลิเคชันส่วนใหญ่ ความปลอดภัยในทางปฏิบัติของ Go อาจเพียงพอ แต่องค์กรที่ต้องการการรับประกันความปลอดภัยอย่างเป็นทางการควรพิจารณาข้อจำกัดเหล่านี้อย่างรอบคอบเมื่อตัดสินใจเลือกเทคโนโลยี
การถกเถียงนี้สะท้อนให้เห็นความเข้าใจที่พัฒนาไปเรื่อยๆ เกี่ยวกับสิ่งที่ถือว่าเป็นการเขียนโปรแกรมที่ปลอดภัย และการแลกเปลี่ยนระหว่างประสิทธิภาพ ความเรียบง่าย และความถูกต้องอย่างเป็นทางการในการพัฒนาซอฟต์แวร์สมัยใหม่