Rust แตกต่างจากสิ่งอำนวยความสะดวกในการทำงานพร้อมกันของ C ++ อย่างไร


35

คำถาม

ฉันพยายามที่จะเข้าใจว่าสนิมช่วยปรับปรุงพื้นฐานและพร้อมกันของสิ่งอำนวยความสะดวกในการทำงานพร้อมกันของ C ++ หรือไม่เพื่อที่จะตัดสินใจว่าฉันควรใช้เวลาในการเรียนรู้สนิมหรือไม่

โดยเฉพาะอย่างยิ่ง idiomatic Rust จะพัฒนาขึ้นอย่างไรหรือในอัตราที่ต่างกันอย่างไรสิ่งอำนวยความสะดวกที่เกิดขึ้นพร้อมกันของ C ++

การปรับปรุง (หรือความแตกต่าง) ส่วนใหญ่เกี่ยวกับการสร้างประโยคหรือเป็นการปรับปรุงที่สำคัญในกระบวนทัศน์หรือไม่? หรือมันเป็นอย่างอื่น? หรือไม่เป็นการปรับปรุงที่แท้จริง (divergence) เลยเหรอ?


หลักการและเหตุผล

ฉันเพิ่งพยายามสอนสิ่งอำนวยความสะดวกในการทำงานพร้อมกันของ C ++ 14 และมีบางสิ่งที่รู้สึกไม่ถูกต้อง บางสิ่งบางอย่างรู้สึกปิด สิ่งที่รู้สึกออก? ยากที่จะพูด.

รู้สึกว่าคอมไพเลอร์ไม่ได้พยายามช่วยฉันในการเขียนโปรแกรมที่ถูกต้องเมื่อพูดถึงการทำงานพร้อมกัน รู้สึกราวกับว่าฉันกำลังใช้แอสเซมเบลอร์มากกว่าคอมไพเลอร์

เป็นที่ยอมรับกันว่าเป็นไปได้ทั้งหมดที่ฉันยังต้องทนทุกข์ทรมานจากแนวคิดที่บอบบางและผิดพลาดเมื่อมันเกิดขึ้นพร้อมกัน บางทีฉันอาจจะยังไม่เห็นความตึงเครียดของ Bartosz Milewski ระหว่างการเขียนโปรแกรมแบบรัฐและการแข่งขันข้อมูล บางทีฉันอาจไม่เข้าใจว่ามีวิธีการทำงานพร้อมกันของเสียงในคอมไพเลอร์มากน้อยแค่ไหนและมันมีอยู่ในระบบปฏิบัติการเท่าใด

คำตอบ:


56

เรื่องการทำงานพร้อมกันที่ดีกว่านั้นเป็นหนึ่งในเป้าหมายหลักของโครงการ Rust ดังนั้นควรมีการปรับปรุงหากเราเชื่อมั่นในโครงการเพื่อให้บรรลุเป้าหมาย ข้อจำกัดความรับผิดชอบเต็มรูปแบบ: ฉันมีความคิดเห็นสูงเกี่ยวกับสนิมและฉันลงทุนกับมัน ตามที่ร้องขอผมจะพยายามที่จะหลีกเลี่ยงการตัดสินคุณค่าและอธิบายความแตกต่างมากกว่า (IMHO) การปรับปรุง

สนิมปลอดภัยและไม่ปลอดภัย

"สนิม" ประกอบด้วยสองภาษา: ภาษาที่พยายามแยกคุณออกจากอันตรายของการเขียนโปรแกรมระบบและภาษาที่มีประสิทธิภาพยิ่งกว่าโดยไม่มีแรงบันดาลใจใด ๆ

Unsafe Rust เป็นภาษาที่น่ารังเกียจและโหดร้ายที่ให้ความรู้สึกเหมือน C ++ ช่วยให้คุณทำสิ่งที่เป็นอันตรายโดยพลการพูดคุยกับฮาร์ดแวร์จัดการข้อผิดพลาดด้วยตนเองถ่ายภาพด้วยตนเองและอื่น ๆ มันเหมือนกับ C และ C ++ ที่ความถูกต้องของโปรแกรมอยู่ในมือคุณ และมือของโปรแกรมเมอร์อื่น ๆ ที่เกี่ยวข้องทั้งหมด คุณเลือกใช้ภาษานี้โดยใช้คำหลักunsafeและใน C และ C ++ ความผิดพลาดเพียงครั้งเดียวในสถานที่เดียวสามารถทำให้โครงการทั้งหมดพัง

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

มันอันตรายใช่ แต่ก็มีความสำคัญเช่นกันเพราะช่วยให้สามารถสร้าง abstractions ที่รหัสที่ปลอดภัยใช้ รหัสที่ไม่ปลอดภัยที่ดีใช้ระบบประเภทเพื่อป้องกันไม่ให้ผู้อื่นใช้ในทางที่ผิดดังนั้นการมีรหัสที่ไม่ปลอดภัยในโปรแกรม Rust ไม่จำเป็นต้องรบกวนรหัสที่ปลอดภัย ความแตกต่างต่อไปนี้มีอยู่ทั้งหมดเนื่องจากระบบประเภทของ Rust มีเครื่องมือที่ C ++ ไม่มีและเนื่องจากรหัสที่ไม่ปลอดภัยซึ่งใช้ abstractcurrency พร้อมกันจะใช้เครื่องมือเหล่านี้อย่างมีประสิทธิภาพ

ไม่แตกต่าง: หน่วยความจำแบบแบ่งใช้ / ไม่แน่นอน

แม้ว่า Rust จะให้ความสำคัญกับการส่งข้อความมากขึ้นและควบคุมหน่วยความจำที่ใช้ร่วมกันอย่างเข้มงวด แต่ก็ไม่ได้ตัดทอนการทำงานพร้อมกันของหน่วยความจำที่ใช้ร่วมกันและสนับสนุน abstractions ทั่วไปอย่างชัดเจน (ล็อค, การทำงานของอะตอม

ยิ่งกว่านั้นเช่น C ++ และแตกต่างจากภาษาที่ใช้งานได้จริง Rust ชอบโครงสร้างข้อมูลที่จำเป็นแบบดั้งเดิม ไม่มีรายการลิงก์ถาวร / ไม่เปลี่ยนแปลงในไลบรารีมาตรฐาน มีstd::collections::LinkedListแต่มันก็เหมือนstd::listใน C ++ และท้อแท้ด้วยเหตุผลเดียวกับstd::list(การใช้แคชไม่ดี)

อย่างไรก็ตามด้วยการอ้างอิงถึงชื่อของส่วนนี้ ("หน่วยความจำแบบแบ่งใช้ / ไม่แน่นอน"), Rust มีความแตกต่างอย่างหนึ่งกับ C ++: ขอแนะนำให้หน่วยความจำนั้นเป็น "ส่วนแบ่ง XOR ที่ไม่แน่นอน" เช่นหน่วยความจำนั้น เวลา. เปลี่ยนความทรงจำตามที่คุณต้องการ "ในความเป็นส่วนตัวของเธรดของคุณ" เพื่อพูด ตัดกับ C ++ โดยที่หน่วยความจำที่ไม่แน่นอนที่แบ่งใช้เป็นตัวเลือกเริ่มต้นและใช้กันอย่างแพร่หลาย

ในขณะที่กระบวนทัศน์ที่ใช้ร่วมกันของ xor-mutable นั้นมีความสำคัญอย่างมากต่อความแตกต่างด้านล่าง แต่มันก็เป็นกระบวนทัศน์การเขียนโปรแกรมที่แตกต่างกันมากซึ่งใช้เวลาสักครู่ในการทำความคุ้นเคย บางครั้งเราต้องยกเลิกกระบวนทัศน์นี้เช่นกับประเภทอะตอม ( AtomicUsizeเป็นสาระสำคัญของหน่วยความจำที่ไม่แน่นอนที่ใช้ร่วมกัน) โปรดทราบว่าการล็อกจะปฏิบัติตามกฎ shared-xor-mutable เนื่องจากจะออกกฎการอ่านและเขียนพร้อมกัน (ในขณะที่การเขียนเธรดหนึ่งเธรดไม่มีเธรดอื่นใดที่สามารถอ่านหรือเขียนได้)

ความแตกต่าง: การแข่งขันข้อมูลเป็นพฤติกรรมที่ไม่ได้กำหนด (UB)

หากคุณทริกเกอร์การแข่งขันของข้อมูลในรหัสสนิมมันจะจบลงเช่นเดียวกับใน C ++ การเดิมพันทั้งหมดปิดและผู้เรียบเรียงสามารถทำสิ่งที่มันพอใจ

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

ความแตกต่าง: วินัยการล็อคที่เข้มงวด

ซึ่งแตกต่างจาก C ++ ล็อคใน Rust ( std::sync::Mutex, std::sync::RwLockฯลฯ ) เป็นเจ้าของข้อมูลก็ปกป้อง แทนที่จะใช้การล็อกจากนั้นจัดการหน่วยความจำที่ใช้ร่วมกันบางอย่างที่เกี่ยวข้องกับการล็อกในเอกสารประกอบเท่านั้นข้อมูลที่แชร์จะไม่สามารถเข้าถึงได้ในขณะที่คุณไม่ได้ล็อคไว้ การ์ดรักษาความปลอดภัย RAII รักษาล็อคและให้การเข้าถึงข้อมูลที่ถูกล็อคพร้อมกัน (สามารถใช้ C ++ ได้ แต่สิ่งนี้ไม่ใช่std::ล็อค) ระบบอายุการใช้งานช่วยให้มั่นใจได้ว่าคุณจะไม่สามารถเข้าถึงข้อมูลได้หลังจากที่คุณปลดล็อค (ดร็อปการ์ด RAII)

แน่นอนคุณสามารถมีล็อคที่ไม่มีข้อมูลที่เป็นประโยชน์ ( Mutex<()>) และเพียงแบ่งปันหน่วยความจำบางส่วนโดยไม่ต้องเชื่อมโยงกับล็อคนั้นอย่างชัดเจน unsafeแต่มีหน่วยความจำที่ใช้ร่วมกันอาจต้อง unsynchronized

ความแตกต่าง: การป้องกันการแบ่งปันโดยไม่ตั้งใจ

แม้ว่าคุณจะสามารถมีหน่วยความจำที่ใช้ร่วมกัน แต่คุณจะแบ่งปันเมื่อคุณขอมันอย่างชัดเจน ตัวอย่างเช่นเมื่อคุณใช้ข้อความผ่าน (เช่นช่องจากstd::sync) เพื่อให้แน่ใจว่าระบบชีวิตของคุณไม่ให้การอ้างอิงใด ๆ กับข้อมูลหลังจากที่คุณส่งมาให้อีกด้วยค่ะ ในการแชร์ข้อมูลเบื้องหลังการล็อคคุณสร้างการล็อคอย่างชัดเจนและให้มันไปยังเธรดอื่น การใช้หน่วยความจำหมู่กับคุณดีต้องใช้unsafeunsafe

สิ่งนี้เชื่อมโยงกับประเด็นต่อไป:

ความแตกต่าง: การติดตามความปลอดภัยของเธรด

ระบบชนิดของสนิมติดตามแนวคิดเรื่องความปลอดภัยของเธรด โดยเฉพาะSyncประเภทลักษณะหมายถึงว่าสามารถใช้ร่วมกันโดยหลายกระทู้โดยไม่มีความเสี่ยงของการแข่งขันข้อมูลในขณะที่Sendเครื่องหมายที่สามารถเคลื่อนย้ายจากที่หนึ่งไปยังอีกด้าย สิ่งนี้ถูกบังคับใช้โดยคอมไพเลอร์ตลอดทั้งโปรแกรมและทำให้ผู้ออกแบบห้องสมุดกล้าทำการปรับแต่งที่อาจเป็นอันตรายอย่างน่าประหลาดใจหากไม่มีการตรวจสอบแบบคงที่เหล่านี้ ตัวอย่างเช่น C ++ std::shared_ptrซึ่งมักจะใช้การดำเนินการปรมาณูเพื่อจัดการจำนวนการอ้างอิงของมันเพื่อหลีกเลี่ยงการ UB ถ้าshared_ptrเกิดขึ้นที่จะใช้โดยหลายกระทู้ สนิมมีRcและArcซึ่งแตกต่างกันเฉพาะในที่Rc ใช้การดำเนินการ refcount ไม่ใช่อะตอมและไม่ threadsafe (เช่นไม่ได้ใช้SyncหรือSend) ในขณะที่Arcเป็นเหมือนshared_ptr (และใช้ทั้งสองลักษณะ)

โปรดทราบว่าหากประเภทไม่ได้ใช้unsafeเพื่อนำไปใช้ในการซิงโครไนซ์ด้วยตนเองการมีหรือไม่มีคุณลักษณะนั้นจะอนุมานได้อย่างถูกต้อง

ความแตกต่าง: กฎที่เข้มงวดมาก

ถ้าคอมไพเลอร์ไม่สามารถแน่ใจจริงๆว่ารหัสบางอย่างเป็นอิสระจากการแข่งขันข้อมูลและ UB อื่น ๆมันจะไม่รวบรวมระยะเวลา กฎดังกล่าวข้างต้นและเครื่องมืออื่น ๆ จะช่วยให้คุณออกไปได้ไกล แต่ไม่ช้าก็เร็วคุณจะต้องการทำสิ่งที่ถูกต้อง แต่ด้วยเหตุผลที่ละเอียดอ่อนซึ่งทำให้การแจ้งเตือนของคอมไพเลอร์หมดไป มันอาจเป็นโครงสร้างข้อมูลที่ปราศจากการล็อคที่ยุ่งยาก แต่ก็อาจเป็นสิ่งที่ทางโลกคิดว่า "ฉันเขียนไปยังตำแหน่งสุ่มในอาร์เรย์ที่ใช้ร่วมกัน

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

ความแตกต่าง: เครื่องมือน้อยลง

เนื่องจากความแตกต่างดังกล่าวข้างต้นใน Rust มันหายากกว่ามากที่หนึ่งเขียนโค้ดที่อาจมีการแข่งขันข้อมูล (หรือใช้หลังจากฟรีหรือสองครั้งฟรีหรือ ... ) แม้ว่าสิ่งนี้จะเป็นสิ่งที่ดี แต่ก็มีผลข้างเคียงที่โชคร้ายที่ระบบนิเวศในการติดตามข้อผิดพลาดดังกล่าวยังด้อยพัฒนามากกว่าที่คาดไว้เนื่องจากเยาวชนและชุมชนมีขนาดเล็ก

ในขณะที่เครื่องมืออย่าง valgrind และ LLVM นั้นสามารถนำไปประยุกต์ใช้กับ Rust code ได้ แต่หลักการนี้ใช้ได้ผลจริง แต่แตกต่างกันไปในแต่ละเครื่องมือ - วันที่ทรัพยากรเกี่ยวกับวิธีการทำ) มันไม่ได้ช่วยอะไรมากนักที่ปัจจุบัน Rust ขาดสเปคที่แท้จริงและโดยเฉพาะอย่างยิ่งรูปแบบหน่วยความจำที่เป็นทางการ

กล่าวโดยย่อการเขียนunsafeรหัส Rust อย่างถูกต้องนั้นยากกว่าการเขียนรหัส C ++ อย่างถูกต้องถึงแม้ว่าทั้งสองภาษานั้นจะมีความสามารถและความเสี่ยงในเชิงเปรียบเทียบกัน แน่นอนว่าสิ่งนี้จะต้องมีการถ่วงน้ำหนักกับความจริงที่ว่าโปรแกรม Rust ทั่วไปจะมีunsafeโค้ดเพียงเล็กน้อยที่มีขนาดเล็กในขณะที่โปรแกรม C ++ นั้นก็คือ C ++ อย่างเต็มที่


6
หน้าจอของฉันอยู่ที่ +25 upvote switch? ฉันหามันไม่เจอ! คำตอบที่ให้ข้อมูลนี้ชื่นชมมาก มันทำให้ฉันไม่มีคำถามที่ชัดเจนเกี่ยวกับประเด็นที่ครอบคลุม ดังนั้นในประเด็นอื่น ๆ : ถ้าฉันเข้าใจเอกสารของ Rust Rust มีสิ่งอำนวยความสะดวกการทดสอบแบบบูรณาการ [a] และ [b] ระบบสร้างชื่อ Cargo สิ่งเหล่านี้พร้อมสำหรับการผลิตในมุมมองของคุณหรือไม่ นอกจากนี้เกี่ยวกับ Cargo มันเป็นเรื่องตลกไหมที่ให้ฉันเพิ่มเชลล์สคริปต์ Python และ Perl การคอมไพล์ LaTeX และอื่น ๆ ให้กับกระบวนการสร้าง
thb

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

2
อย่างไรก็ตามคำตอบของคุณนั้นค่อนข้างชัดเจน เนื่องจากฉันชอบ C ++ เนื่องจาก C ++ มีสิ่งอำนวยความสะดวกที่ดีสำหรับเกือบทุกอย่างที่ฉันต้องทำเนื่องจาก C ++ นั้นมีเสถียรภาพและใช้กันอย่างแพร่หลายดังนั้นฉันจึงพอใจที่จะใช้ C ++ สำหรับวัตถุประสงค์ที่ไม่มีน้ำหนักเบาที่เป็นไปได้ทั้งหมด , ตัวอย่างเช่น). แต่ตอนนี้เรามีการเห็นพ้องด้วยและ C ++ 14 ดูเหมือนว่าฉันจะดิ้นรนกับมัน ฉันไม่ได้ลองภาษาโปรแกรมใหม่โดยสมัครใจในทศวรรษ แต่ (เว้นแต่ว่า Haskell ควรปรากฏตัวเลือกที่ดีกว่า) ฉันคิดว่าฉันต้องลองใช้ Rust
บาท

Note that if a type doesn't use unsafe to manually implement synchronization, the presence or absence of the traits are inferred correctly.ที่จริงมันยังคงทำแม้จะมีunsafeองค์ประกอบ เพียงแค่ชี้ดิบไม่ได้Syncมิได้Shareซึ่งหมายความว่าโดย struct เริ่มต้นมีพวกเขาจะมีค่า
Hauleth

@ ŁukaszNiemierมันสามารถเกิดขึ้นได้กับการทำงานได้ดี แต่มีหลายพันล้านวิธีที่ประเภทที่ไม่ปลอดภัยที่ใช้อาจจบลงSendหรือSyncแม้ว่ามันจะไม่ควร

-2

สนิมก็เหมือน Erlang และ Go มาก มันสื่อสารโดยใช้ช่องทางที่มีบัฟเฟอร์และรอตามเงื่อนไข เช่นเดียวกับ Go มันช่วยผ่อนคลายข้อ จำกัด ของ Erlang โดยให้คุณแชร์หน่วยความจำสนับสนุนการนับจำนวนอะตอมและล็อคและให้คุณส่งช่องจากเธรดหนึ่งไปยังเธรด

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

ต่อไปนี้เป็นคำพูดจากเอกสาร RUST

กฎการเป็นเจ้าของมีบทบาทสำคัญในการส่งข้อความเพราะช่วยให้เราเขียนรหัสที่ปลอดภัยและพร้อมกัน การป้องกันข้อผิดพลาดในการเขียนโปรแกรมพร้อมกันเป็นข้อได้เปรียบที่เราได้รับจากการทำให้การแลกเปลี่ยนไม่ต้องคิดเกี่ยวกับการเป็นเจ้าของตลอดทั้งโปรแกรม Rust ของเรา - ข้อความที่ส่งด้วยความเป็นเจ้าของค่า

หาก Erlang เป็น draconian และ Go เป็นรัฐอิสระดังนั้น Rust เป็นรัฐพี่เลี้ยง

คุณสามารถค้นหาข้อมูลเพิ่มเติมได้จากแนวคิดการทำงานพร้อมกันของภาษาโปรแกรม: Java, C #, C, C +, Go และ Rust


2
ยินดีต้อนรับสู่ Stack Exchange! โปรดทราบว่าเมื่อใดก็ตามที่คุณเชื่อมโยงไปยังบล็อกของคุณเองคุณต้องระบุอย่างชัดเจน ดูศูนย์ช่วยเหลือ
Glorfindel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.