Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

คอนเคอร์เรนซีที่ขยายความสามารถได้ด้วย Send และ Sync

น่าสนใจที่ฟีเจอร์ด้านคอนเคอร์เรนซีเกือบทุกอย่างที่เราพูดถึงในบทนี้จนถึงตอนนี้ล้วนเป็นส่วนหนึ่งของไลบรารีมาตรฐาน ไม่ใช่ตัวภาษาเขียนโปรแกรม ตัวเลือกของคุณในการจัดการคอนเคอร์เรนซีไม่ได้จำกัดอยู่เพียงแค่ภาษาหรือไลบรารีมาตรฐานเท่านั้น คุณสามารถเขียนฟีเจอร์คอนเคอร์เรนซีของคุณเองหรือใช้ฟีเจอร์ที่ผู้อื่นเขียนขึ้นได้

อย่างไรก็ตาม ในบรรดาแนวคิดหลักด้านคอนเคอร์เรนซีที่ฝังอยู่ในภาษามากกว่าไลบรารีมาตรฐานนั้น คือเทรต std::marker ได้แก่ Send และ Sync

การถ่ายโอนความเป็นเจ้าของระหว่างเธรด

เทรตกำกับ (marker trait) Send ระบุว่าความเป็นเจ้าของของค่าของชนิดข้อมูลที่ใช้เทรต Send สามารถถ่ายโอนระหว่างเธรดได้ ชนิดข้อมูลใน Rust เกือบทั้งหมดใช้เทรต Send แต่ก็มีข้อยกเว้นบางประการ รวมถึง Rc<T>: ชนิดข้อมูลนี้ไม่สามารถใช้เทรต Send ได้ เนื่องจากหากคุณโคลนค่า Rc<T> และพยายามถ่ายโอนความเป็นเจ้าของของโคลนนั้นไปยังอีกเธรดหนึ่ง ทั้งสองเธรดอาจอัปเดตจำนวนการอ้างอิงไปพร้อมๆ กัน ด้วยเหตุนี้ Rc<T> จึงถูกสร้างขึ้นมาสำหรับการใช้งานในสถานการณ์เธรดเดียวที่คุณไม่อยากเสียประสิทธิภาพเพิ่มเติมจากความปลอดภัยของเธรด

ดังนั้น ระบบชนิดข้อมูลและขอบเขตเทรต (trait bounds) ของ Rust จึงช่วยให้แน่ใจว่าคุณจะไม่เผลอนำส่งค่า Rc<T> ข้ามเธรดอย่างไม่ปลอดภัยโดยเด็ดขาด เมื่อเราพยายามทำเช่นนั้นในโค้ดตัวอย่างที่ 16-14 เราจึงได้รับข้อผิดพลาด the trait `Send` is not implemented for `Rc<Mutex<i32>>` และเมื่อเราเปลี่ยนไปใช้ Arc<T> ซึ่งใช้เทรต Send โค้ดก็สามารถคอมไพล์ได้

ชนิดข้อมูลใดก็ตามที่ประกอบด้วยชนิดข้อมูลที่เป็น Send ทั้งหมด จะถูกทำเครื่องหมายเป็น Send โดยอัตโนมัติ ชนิดข้อมูลพริมิทิฟเกือบทั้งหมดเป็น Send ยกเว้นพอยน์เตอร์ดิบ (raw pointers) ซึ่งเราจะพูดถึงในบทที่ 20

การเข้าถึงจากหลายเธรด

เทรตกำกับ Sync ระบุว่าชนิดข้อมูลที่ใช้เทรต Sync นั้นปลอดภัยในการถูกอ้างอิงจากหลายเธรด หรือกล่าวอีกนัยหนึ่ง ชนิดข้อมูล T ใดๆ จะใช้เทรต Sync หาก &T (การอ้างอิงแบบเปลี่ยนแปลงไม่ได้ไปยัง T) ใช้เทรต Send ซึ่งหมายความว่าการอ้างอิงนั้นสามารถส่งไปยังอีกเธรดหนึ่งได้อย่างปลอดภัย คล้ายกับ Send ชนิดข้อมูลพริมิทิฟทั้งหมดใช้เทรต Sync และชนิดข้อมูลที่ประกอบด้วยชนิดข้อมูลที่ใช้เทรต Sync ทั้งหมดก็จะใช้เทรต Sync ด้วยเช่นกัน

พอยน์เตอร์อัจฉริยะ Rc<T> ไม่ได้ใช้เทรต Sync ด้วยเหตุผลเดียวกับที่ไม่ใช้เทรต Send ชนิดข้อมูล RefCell<T> (ซึ่งเราพูดถึงในบทที่ 15) และตระกูลชนิดข้อมูล Cell<T> ที่เกี่ยวข้องไม่ได้ใช้เทรต Sync การตรวจสอบการยืมที่ RefCell<T> ทำในขณะโปรแกรมทำงาน (runtime) ไม่ได้ปลอดภัยต่อเธรด ส่วนพอยน์เตอร์อัจฉริยะ Mutex<T> ใช้เทรต Sync และสามารถนำไปใช้แบ่งปันการเข้าถึงกับหลายเธรดได้ ดังที่คุณได้เห็นในหัวข้อ “การเข้าถึง Mutex<T> ร่วมกัน”

การปรับแต่งเทรต Send และ Sync ด้วยตนเองเป็นสิ่งที่ไม่ปลอดภัย (Unsafe)

เนื่องจากชนิดข้อมูลที่ประกอบด้วยชนิดข้อมูลอื่นที่ใช้เทรต Send และ Sync ทั้งหมดจะใช้เทรต Send และ Sync โดยอัตโนมัติ เราจึงไม่ต้องปรับแต่งเทรตเหล่านั้นด้วยตนเอง ในฐานะเทรตกำกับ พวกมันไม่มีแม้กระทั่งเมธอดให้ต้องเขียนการทำงานใดๆ พวกมันมีประโยชน์เพียงเพื่อบังคับใช้อนุรักษ์คุณสมบัติ (invariants) ที่เกี่ยวข้องกับคอนเคอร์เรนซีเท่านั้น

การปรับแต่งการใช้เทรตเหล่านี้ด้วยตนเองจำเป็นต้องเขียนโค้ด Rust ที่ไม่ปลอดภัย (unsafe Rust code) เราจะพูดถึงการใช้โค้ด Rust ที่ไม่ปลอดภัยในบทที่ 20 สำหรับตอนนี้ ข้อมูลสำคัญคือการสร้างชนิดข้อมูลคอนเคอร์เรนต์ใหม่ที่ไม่ได้ประกอบจากส่วนที่เป็น Send และ Sync จำเป็นต้องใช้ความคิดอย่างรอบคอบเพื่อรักษาการรับประกันความปลอดภัยเอาไว้ “The Rustonomicon” มีข้อมูลเพิ่มเติมเกี่ยวกับการรับประกันเหล่านี้และวิธีรักษามันไว้

สรุป

นี่ไม่ใช่ครั้งสุดท้ายที่คุณจะได้เห็นเรื่องคอนเคอร์เรนซีในหนังสือเล่มนี้: บทถัดไปจะเน้นเรื่องการเขียนโปรแกรมแบบอะซิงโครนัส (async programming) และโปรเจกต์ในบทที่ 21 จะนำแนวคิดในบทนี้ไปใช้ในสถานการณ์ที่สมจริงมากกว่าตัวอย่างขนาดเล็กที่คุยกันในที่นี้

ดังที่กล่าวไว้ก่อนหน้านี้ เนื่องจากวิธีการจัดการคอนเคอร์เรนซีของ Rust มีส่วนที่เป็นตัวภาษาน้อยมาก โซลูชันคอนเคอร์เรนซีจำนวนมากจึงถูกเขียนขึ้นในรูปแบบของเครต (crates) สิ่งเหล่านี้มีการพัฒนาเร็วกว่าไลบรารีมาตรฐาน ดังนั้นอย่าลืมค้นหาออนไลน์สำหรับเครตที่ทันสมัยและเป็นเทคโนโลยีล่าสุดสำหรับใช้งานในสถานการณ์แบบหลายเธรด

ไลบรารีมาตรฐานของ Rust จัดหาแชนเนลสำหรับการส่งข้อความและชนิดข้อมูลพอยน์เตอร์อัจฉริยะ เช่น Mutex<T> และ Arc<T> ซึ่งปลอดภัยสำหรับการใช้งานในบริบทแบบคอนเคอร์เรนต์ ระบบชนิดข้อมูลและตัวตรวจสอบการยืมช่วยรับประกันว่าโค้ดที่ใช้โซลูชันเหล่านี้จะไม่จบลงด้วยภาวะแย่งชิงข้อมูล (data races) หรือการอ้างอิงที่ไม่ถูกต้อง เมื่อคุณทำให้โค้ดคอมไพล์ผ่านได้แล้ว คุณก็มั่นใจได้เลยว่าโค้ดจะทำงานบนหลายเธรดได้อย่างราบรื่นโดยไม่มีบั๊กที่ติดตามยากอย่างที่พบบ่อยในภาษาอื่นๆ การโปรแกรมแบบคอนเคอร์เรนซีไม่ใช่แนวคิดที่ต้องกลัวอีกต่อไป: จงก้าวออกไปและทำให้โปรแกรมของคุณทำงานแบบคอนเคอร์เรนต์ได้อย่างไร้กังวล!