محتويات الدورة

Rust / المتقدم / الدرس 14

الخيوط وSend وSync والحالة المشتركة

انقل الملكية بين الخيوط واحمِ انتقالات الحالة كاملة واشرح بدقة ضمانات Arc وسمات المزامنة وحدودها.

وقت القراءة التقريبي: 4 دقيقةتاريخ مراجعة المحتوى
في هذه الصفحة

أهداف الدرس

  • تفسير Send وSync كخاصيتي أمان للأنواع.
  • استخدام Arc<Mutex<T>> وتمرير الرسائل بإغلاق صريح.
  • تمييز غياب سباق البيانات والصحة المنطقية وغياب التعطل المتبادل.

تصف Send وSync انتقالين مختلفين

تعني Send إمكان نقل القيمة بأمان بين الخيوط، وتعني Sync إمكان إرسال مراجعها المشتركة؛ فـT هي Sync عندما تكون &T هي Send. تُستنتج السمات التلقائية من البنية مع قواعد خاصة، وتطبيقها يدويًا ادعاء unsafe يحتاج إثباتًا. لا تناسب Rc ذات العد غير الذري المشاركة بين الخيوط. تستخدم Arc عدًا ذريًا ولا تمنح إذنًا عامًا للوصول المتزامن لأي T؛ فيجب أن يستوفي الداخل قيود Send وSync اللازمة.

حماية الخاصية كاملة بقفل واحد

توفر Mutex وصولًا حصريًا بحارس يضبط عمر فتح القفل، وتشارك Arc ملكيته بين العمال. ينفذ المثال مئتي زيادة ويضم العمال قبل القراءة. تختلف الجدولة وتثبت النتيجة لحماية كل زيادة. توثق unwrap غياب panic وتسمم القفل المتوقع في العرض المغلق؛ وتحتاج الخدمة سياسة فشل وتسمم. تجنب الردود المجهولة والعمل الحاجب الطويل تحت القفل إلا بمتطلب صريح.

rust ملكية مشتركة لحالة مضبوطة
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
    let count = Arc::new(Mutex::new(0u32));
    let mut workers = Vec::new();
    for _ in 0..2 {
        let count = Arc::clone(&count);
        workers.push(thread::spawn(move || {
            for _ in 0..100 { *count.lock().unwrap() += 1; }
        }));
    }
    for worker in workers { worker.join().unwrap(); }
    println!("{}", *count.lock().unwrap());
}
الناتج المتوقع
200

يسهم كل عامل بمئة زيادة محمية، ويُضمّان قبل القراءة النهائية تحت الحراسة.

نقل الرسائل بدل مشاركة السجلات المتغيرة

تنقل القنوات رسائل مملوكة من المنتج إلى المستقبل. يملك إغلاق move المرسل الملتقط وينقل إرسال String الرسالة للقناة. يسقط المثال المرسل الأصلي لينتهي التكرار بزوال مرسلي العمال. قد يصل المنتجان بأي ترتيب فيفرز المستقبل الرسائل قبل التقرير الحتمي. إغلاق القناة جزء من التصميم؛ فقد يبقي مرسل محفوظ دون انتباه المستقبل منتظرًا بعد نهاية العمل المقصود.

rust وصول غير حتمي وعرض حتمي
use std::sync::mpsc;
use std::thread;
fn main() {
    let (sender, receiver) = mpsc::channel();
    let mut workers = Vec::new();
    for name in ["south", "north"] {
        let sender = sender.clone();
        workers.push(thread::spawn(move || sender.send(String::from(name)).unwrap()));
    }
    drop(sender);
    let mut messages: Vec<String> = receiver.into_iter().collect();
    for worker in workers { worker.join().unwrap(); }
    messages.sort();
    println!("{:?}", messages);
}
الناتج المتوقع
["north", "south"]

تملك القناة السلاسل المنقولة، ويزيل الفرز اعتماد الإخراج على ترتيب الوصول.

اختيار الأعمار ونطاق الذرية بدقة

تتطلب thread::spawn قيود static عادة لاحتمال تجاوز العامل عمر المستدعي. تسمح الخيوط ذات النطاق باستعارة بيانات أقصر إذا ضمنت اكتمال العمل قبل انتهاء العمر. تدعم الذريات عمليات محددة لا تتجزأ، ولا تحفظ ذريتان خاصية مشتركة تلقائيًا. لا يساوي load ثم store العملية fetch_add. ابدأ بقفل للحالة المركبة ولا تضعف ترتيب الذرية إلا بحجة نشر وعمر مكتوبة. لا يزامن العد المرجعي الآمن التعديل العادي للداخل.

أمان الذاكرة هدف تزامن واحد

تمنع Rust الآمنة سباقات البيانات إذا احترمت الاعتماديات unsafe عقودها، وقد تبقى الأقفال المتبادلة والجوع والتحديثات المنطقية المفقودة وعدم الحتمية. حدّد ترتيب الأقفال وحدود الطوابير والإلغاء والضم. تجنب الحراس عبر استقبال حاجب أو تعليق async دون بروتوكول مبرر. اختبر الإغلاق بلا رسائل ومع فشل العمال. تساعد السجلات والتجميع الحتمي دون وعد بتداخل ثابت. لا يتحقق المترجم من صدق تطبيق Send أو Sync يدويًا نيابة عنك.

الأخطاء الشائعة وإصلاحها

توقع مشاركة الخيوط عبر Arc<RefCell<T>>.

لماذا يحدث؟
خُلطت الملكية الذرية بمزامنة الوصول الداخلي.
كيف تصلحه؟
استخدم داخلًا مضبوط المزامنة أو ملكية رسائل.
كيف تتجنب تكراره؟
افحص شروط Send وSync للداخل.

المستقبل ينتظر بعد اكتمال العمال.

لماذا يحدث؟
بقي مرسل إضافي حيًا.
كيف تصلحه؟
أسقط المرسلين غير اللازمين وحدّد ملكية الإغلاق.
كيف تتجنب تكراره؟
اختبر الإغلاق بلا عمل صراحة.

حقلان ذريان يخالفان قاعدة مشتركة.

لماذا يحدث؟
لم تُزامن العملية الكاملة.
كيف تصلحه؟
احمِ الحقول بعملية تحفظ الخاصية المشتركة.
كيف تتجنب تكراره؟
راجع المعاملات المنطقية مستقلة عن سباقات البيانات.

اختبر فهمك

1. احذف الفرز من مثال القناة. ما الخاصية المضمونة وما التي لا يجوز الوعد بها؟

اعرض الحل المشروح

يتلقى التشغيل الناجح String باسم north وأخرى south، وقد يعتمد ترتيبهما على الجدولة. كلا الترتيبين صالح؛ قارن مجموعة أو افرز قبل تأكيد السطر. هذا عدم حتمية عادي لا سباق بيانات. أبقِ بروتوكول إسقاط المرسل والضم.

2. يقرأ عامل متجهًا محليًا فقط أثناء عملية محدودة. قارن استنساخه إلى thread::spawn واستعارته بخيوط ذات نطاق.

اعرض الحل المشروح

ينشئ الاستنساخ بيانات مستقلة بكلفة نسخ وتخصيص. تستعير الخيوط ذات النطاق المتجه إذا ضمنت اكتمالها قبل إسقاطه، مع توافق الاستعارة والوصولات الأخرى ومعالجة الفشل. اختر الملكية من العمر الحقيقي بدل فرض static بتسريب ذاكرة.

أهم ما تعلمته

  • تشارك Arc الملكية وتحدد الأنواع الداخلية الوصول الآمن.
  • زامن الخصائص كاملة وحدّد الإغلاق.
  • لا يثبت غياب السباق الحيوية أو صحة العمل.

المراجع الرسمية