- ในชุมชน Rust มักจะเห็นคำถามบ่อย ๆ ว่า ถ้าเธรดทำทุกอย่างที่
async/await ทำได้ แถมยังง่ายกว่า แล้วทำไมถึงต้องเลือก async/await?
- Rust เป็นภาษาแบบ low-level และไม่ได้ซ่อนความซับซ้อนของ coroutine เอาไว้ ซึ่งเป็นแนวคิดตรงข้ามกับภาษาอย่าง Go ที่ทำให้ทุกอย่างเป็นอะซิงโครนัสโดยปริยายจนโปรแกรมเมอร์แทบไม่ต้องคิดเรื่องอะซิงก์
- โปรแกรมเมอร์ที่ฉลาดมักพยายามหลีกเลี่ยงความซับซ้อน แล้วเหตุใด
async/await จึงยังจำเป็น?
ทำความเข้าใจพื้นหลัง
- Rust เป็นภาษาแบบ low-level โค้ดจึงมักทำงานเป็นเส้นตรง โดยเมื่อหนึ่งงานเสร็จแล้วจึงค่อยทำอีกงานหนึ่ง
- แต่เมื่อจำเป็นต้องรันหลายงานพร้อมกัน เช่น ในเว็บเซิร์ฟเวอร์ โค้ดแบบเส้นตรงจะเริ่มมีปัญหา
- เว็บยุคแรก ๆ พยายามแก้ปัญหานี้ด้วยการนำเธรดมาใช้
- แม้จะใช้เธรดเพื่อรองรับไคลเอนต์หลายรายพร้อมกันได้ แต่โปรแกรมเมอร์ก็ต้องการย้ายความสามารถด้าน concurrency จากพื้นที่ของ OS มาอยู่ใน user space
ปัญหาเรื่อง timeout
- จุดแข็งที่สุดอย่างหนึ่งของ Rust คือ composability
async/await ทำให้สามารถนำ composability นี้มาใช้กับฟังก์ชันที่เป็น I/O-bound ได้
- ตัวอย่างเช่น หากต้องการเพิ่ม timeout ให้กับฟังก์ชันจัดการไคลเอนต์ ก็สามารถทำได้ด้วยการใช้ combinator สองตัว
เธรดเชิงธีม
- การทำ timeout ในตัวอย่างที่ใช้เธรดนั้นไม่ใช่เรื่องง่าย
TcpStream มีฟังก์ชัน set_read_timeout และ set_write_timeout ให้ใช้ แต่แนวทางนี้ก็มีข้อจำกัด
- บทความนำเสนอวิธีเขียน timeout โดยใช้ combinator ของ Rust แต่ก็ยังจำกัดอยู่กับ
TcpStream และต้องมี system call เพิ่มเติม
กรณีความสำเร็จของ Async
- ระบบนิเวศ HTTP ได้เลือกใช้
async/await เป็นกลไกหลักของ runtime
tower เป็นตัวอย่างที่แสดงพลังของ async/await ได้ชัดเจน โดยมีทั้ง timeout, rate limiting และ load balancing
macroquad ซึ่งเป็นเอนจินเกมของ Rust ก็รันเอนจินด้วย async/await เช่นกัน
ปรับภาพลักษณ์ของ Async
- เนื่องจากข้อดีของ
async ยังไม่เป็นที่รับรู้อย่างกว้างขวาง บางคนจึงอาจเข้าใจผิดได้
- ชุมชน Rust มักประเมินข้อดีด้านประสิทธิภาพของ async Rust สูงเกินไป และกลับลดทอนข้อดีที่มีความหมายจริงลง
- ควรมอง
async/await ว่าเป็นโมเดลการเขียนโปรแกรมที่ทรงพลัง ซึ่งสามารถถ่ายทอดรูปแบบที่ใน Rust แบบ synchronous ต้องใช้เธรดจำนวนมากและแชนเนลจำนวนมากจึงจะเขียนได้ ให้กระชับลงอย่างมาก
ความเห็นของ GN⁺
async/await เพิ่มความซับซ้อนของโค้ดเมื่อต้องจัดการ concurrency แต่ในขณะเดียวกันก็ทำให้สามารถรองรับไคลเอนต์จำนวนมากได้อย่างมีประสิทธิภาพ
- บทความนี้เน้นว่า
async/await ไม่ได้มีดีแค่เรื่องประสิทธิภาพ แต่ยังมีจุดแข็งในฐานะโมเดลการเขียนโปรแกรมด้วย
async/await ของ Rust มอบ composability สำหรับงาน I/O หลากหลายรูปแบบ ซึ่งมีประโยชน์อย่างยิ่งในงานอย่างบริการเครือข่ายหรือเว็บเซิร์ฟเวอร์
- หากมองอย่างวิพากษ์ ความซับซ้อนของ
async/await อาจเป็นกำแพงสำหรับนักพัฒนามือใหม่ และจำเป็นต้องมีความพยายามด้านการสอนเพื่อช่วยให้ก้าวข้ามจุดนี้
- โปรเจ็กต์อื่นที่ให้ความสามารถคล้ายกัน ได้แก่การทำ
async/await ของ Node.js และไลบรารี asyncio ของ Python ซึ่งต่างก็มอบพาราไดม์ลักษณะใกล้เคียงกัน
- เมื่อนำ
async/await มาใช้ ควรพิจารณาความซับซ้อนและความสามารถในการบำรุงรักษาของโค้ดร่วมด้วย แต่หากต้องรองรับไคลเอนต์จำนวนมากพร้อมกัน โมเดลนี้ก็ให้ข้อได้เปรียบอย่างมาก
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
Async/await และเธรดเดี่ยว
Async/await เทียบกับเธรด
ปัญหาของบทความ
ประเด็นที่ไม่ได้พูดถึง
ประเด็นสำคัญเรื่องการยกเลิก
แคมเปญคล้ายการตลาดให้กับ async/await
Async/await เทียบกับ fiber
ข้อดีหลักของ async/await ใน Rust
ความเข้าใจผิดเกี่ยวกับ async/await
เหตุผลที่เลือก async/await แทนเธรด