0Pricing
Learn Rust Coding · درس

قنوات mpsc

الإرسال بين الخيوط

قنوات mpsc درس مجاني في Learn Rust Coding على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Learn Rust Coding، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Learn Rust Coding 4 دروس في المجموع.

ما القناة؟

إن القناة أنبوب أحادي الاتجاه لإرسال القيم من خيط إلى آخر. توفر مكتبة Rust القياسية std::sync::mpsc، حيث تعني mpsc «منتِجات متعددة، مستهلِك واحد».

  • يدفع النصف Sender القيم إلى الداخل.
  • يسحب النصف Receiver القيم إلى الخارج.

تتيح القنوات للخيوط التواصل عبر تمرير الرسائل بدلًا من مشاركة الذاكرة مباشرة، مما يتجنب كثيرًا من حالات تسابق البيانات.

إنشاء قناة

استدعوا mpsc::channel() للحصول على زوج (Sender, Receiver). نرسل هنا قيمة واحدة من خيط مُنشأ إلى الخيط الرئيسي.

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        tx.send(42).unwrap();
    });
    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}

send و recv

يعيد tx.send(value) قيمة من النوع Result؛ ولا يفشل إلا إذا أُسقط المستقبل. أما rx.recv() فيحجب التنفيذ حتى وصول قيمة، ويعيد Err عند زوال جميع المُرسِلين.

  • تنقل send ملكية القيمة إلى القناة.
  • تأخذ recv الملكية من الطرف الآخر.
use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        let msg = String::from("hello from thread");
        tx.send(msg).unwrap();
    });
    let text = rx.recv().unwrap();
    println!("{}", text);
}

انتقال الملكية عبر القناة

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

فيما يلي، ستؤدي محاولة طباعة msg بعد send إلى خطأ في الترجمة، ولذلك نستخدمها مرة واحدة فقط.

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        let data = vec![1, 2, 3];
        tx.send(data).unwrap();
        // data is moved; cannot use it here
    });
    let v = rx.recv().unwrap();
    println!("Sum: {}", v.iter().sum::<i32>());
}

التكرار على مستقبل

ينفذ Receiver السمة IntoIterator. ويؤدي التكرار عليه إلى الحصول على كل قيمة حتى تُغلق القناة (أي تُسقط جميع المُرسِلات). وهذه هي الطريقة الاصطلاحية لاستهلاك تدفق من الرسائل.

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        for i in 1..=3 {
            tx.send(i).unwrap();
        }
    });
    for received in rx {
        println!("Received: {}", received);
    }
}

منتِجات متعددة باستخدام clone

تعني mp في mpsc إمكانية وجود عدة مُرسِلات. استنسخوا Sender وامنحوا نسخة لكل خيط. ويجمع المستقبل كل شيء حتى تُسقط جميع النسخ.

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();
    let tx2 = tx.clone();
    thread::spawn(move || { tx.send("from A").unwrap(); });
    thread::spawn(move || { tx2.send("from B").unwrap(); });
    for msg in rx {
        println!("{}", msg);
    }
}

دلالات إغلاق القناة

تنتهي حلقة المستقبل تلقائيًا عند إسقاط جميع المُرسِلات. وإذا ظل Sender واحد على قيد الحياة، فستحجب for msg in rx التنفيذ إلى الأبد بانتظار المزيد. احرصوا دائمًا على إسقاط المُرسِلات أو تحديد نطاقها بشكل صحيح حتى تنتهي الحلقة.

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();
    let handle = thread::spawn(move || {
        for i in 0..3 {
            tx.send(i * 10).unwrap();
        }
        // tx dropped here, closing the channel
    });
    handle.join().unwrap();
    let total: i32 = rx.iter().sum();
    println!("Total: {}", total);
}

‏try_recv للقراءة غير الحاجبة

يحجب recv التنفيذ، أما try_recv فيعيد النتيجة فورًا ضمن Result. ويعيد Ok(value) إذا كانت الرسالة جاهزة، أو Err إذا كانت القناة فارغة أو منقطعة الاتصال. وهذا مفيد في حلقات الأحداث التي يجب أن تواصل تنفيذ أعمال أخرى.

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        thread::sleep(Duration::from_millis(50));
        tx.send("ready").unwrap();
    });
    loop {
        match rx.try_recv() {
            Ok(msg) => { println!("{}", msg); break; }
            Err(_) => println!("waiting..."),
        }
        thread::sleep(Duration::from_millis(20));
    }
}

إرسال الأنواع المخصصة

يمكن لأي نوع ينفذ Send أن ينتقل عبر قناة، بما في ذلك البنى والتعدادات التي تنشئونها بأنفسكم. وتناسب التعدادات نمذجة أنواع الرسائل المختلفة في بروتوكول العامل.

use std::sync::mpsc;
use std::thread;

enum Job {
    Print(String),
    Add(i32, i32),
}

fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        tx.send(Job::Print(String::from("hi"))).unwrap();
        tx.send(Job::Add(2, 3)).unwrap();
    });
    for job in rx {
        match job {
            Job::Print(s) => println!("print: {}", s),
            Job::Add(a, b) => println!("add: {}", a + b),
        }
    }
}

‏sync_channel والتحكم في التدفق

تنشئ mpsc::sync_channel(n) قناة محدودة بحجم مخزن مؤقت قدره n. وعندما يمتلئ المخزن المؤقت، تحجب send التنفيذ حتى تتوفر مساحة. وهذا يوفر تحكمًا عكسيًا في التدفق، فيمنع منتجًا سريعًا من إرهاق مستهلك بطيء.

  • إن sync_channel(0) قناة لقاء: يجب أن يتزامن الإرسال والاستقبال.
use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::sync_channel(2);
    thread::spawn(move || {
        for i in 1..=4 {
            tx.send(i).unwrap();
            println!("sent {}", i);
        }
    });
    for v in rx {
        println!("got {}", v);
    }
}

نمط عامل بسيط

تتألق القنوات في نمط المنتج/المستهلك: إذ ينتج خيط عناصر العمل، بينما يستهلكها خيط آخر ويعالجها. ينتج الخيط الرئيسي هنا أرقامًا، ويربّع العامل كل رقم ثم يعيد النتيجة عبر قناة ثانية.

use std::sync::mpsc;
use std::thread;

fn main() {
    let (job_tx, job_rx) = mpsc::channel();
    let (res_tx, res_rx) = mpsc::channel();
    thread::spawn(move || {
        for n in job_rx {
            res_tx.send(n * n).unwrap();
        }
    });
    for n in 1..=4 {
        job_tx.send(n).unwrap();
    }
    drop(job_tx);
    for r in res_rx {
        println!("square: {}", r);
    }
}

اختبار سريع

اختبروا مدى فهمكم لقنوات mpsc.

مراجعة

لقد تعلمتم أساسيات قنوات mpsc:

  • تعيد mpsc::channel() زوجًا من (Sender, Receiver).
  • تنقل send قيمة إلى الداخل، بينما يحجب recv التنفيذ لأخذها إلى الخارج.
  • يستهلك التكرار على المستقبل الرسائل حتى تُسقط جميع المُرسِلات.
  • استنسخوا Sender لإنشاء عدة منتِجات.
  • لا يحجب try_recv التنفيذ، بينما يضيف sync_channel(n) تحكمًا عكسيًا محدودًا في التدفق.

تتيح القنوات للخيوط مشاركة البيانات بأمان عبر نقل الملكية بدلًا من مشاركة الذاكرة.

الأسئلة الشائعة

هل درس «قنوات mpsc» مجاني؟

نعم — نص درس «قنوات mpsc» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Learn Rust Coding، انتقل إلى CoddyKit PRO. تتضمن دورة Learn Rust Coding 4 دروس في المجموع.

ماذا ستتعلم في «قنوات mpsc»؟

الإرسال بين الخيوط تتمرن على Learn Rust Coding مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Learn Rust Coding؟

لا تُشترط خبرة سابقة. Learn Rust Coding على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «قنوات mpsc»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Learn Rust Coding هذا؟

نعم. كل درس في Learn Rust Coding يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. قنوات mpsc
  2. مشاركة الحالة باستخدام Arc/Mutex
  3. الخيوط محددة النطاق
  4. قنوات Crossbeam
← العودة إلى Learn Rust Coding