0Pricing
Learn Rust Coding · درس

التزامن الآمن مع الخيوط

تعمّقوا في أساسيات البرمجة المتزامنة في Rust، بما فيها الخيوط وتمرير الرسائل، مع ضمان خلو الشيفرة من سباقات البيانات.

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

مقدمة إلى التزامن

مرحبًا بكم في التزامن الآمن باستخدام الخيوط! سنتعلّم في هذا الدرس كيفية جعل برامج Rust تنفّذ عدة مهام في الوقت نفسه.

يعني التزامن تنفيذ حسابات متعددة كما لو أنها تحدث في الوقت نفسه. وهو ضروري للتطبيقات سريعة الاستجابة وللاستفادة من المعالجات الحديثة متعددة الأنوية.

  • الخيوط وحدات تنفيذ خفيفة الوزن داخل البرنامج.
  • يمكن لكل خيط تشغيل جزء منفصل من شيفرتكم.
  • يساعد نظام الملكية في Rust على منع أخطاء التزامن الشائعة.

إنشاء أول خيط لكم

توفر Rust الدالة std::thread::spawn لإنشاء خيوط جديدة. وتمرّرون إليها إغلاقًا (دالة مجهولة) يحتوي على الشيفرة التي ينبغي للخيط الجديد تشغيلها.

جرّبوا تشغيل هذا المثال لرؤية الخيوط عمليًا:

use std::thread;
use std::time::Duration;

fn main() {
  println!("Hello from the main thread!");

  thread::spawn(|| {
    for i in 1..=5 {
      println!("Hi number {} from the spawned thread!", i);
      thread::sleep(Duration::from_millis(1));
    }
  });

  for i in 1..=3 {
    println!("Hi number {} from the main thread!", i);
    thread::sleep(Duration::from_millis(1));
  }
}

انتظار الخيوط باستخدام `join`

في المثال السابق، قد لا ينتهي الخيط الذي أُنشئ قبل خروج الخيط الرئيسي. ويحدث ذلك لأن الخيط الرئيسي لا ينتظر الخيوط المُنشأة افتراضيًا.

لضمان إكمال الخيط المُنشأ لعمله، نستخدم الأسلوب join() في JoinHandle الخاص به. ويوقف ذلك الخيط الحالي مؤقتًا حتى ينتهي الخيط الذي جرى ضمّه.

use std::thread;
use std::time::Duration;

fn main() {
  let handle = thread::spawn(|| {
    for i in 1..=5 {
      println!("Thread: {}", i);
      thread::sleep(Duration::from_millis(1));
    }
  });

  for i in 1..=3 {
    println!("Main: {}", i);
    thread::sleep(Duration::from_millis(1));
  }

  handle.join().unwrap(); // Wait for the spawned thread to finish
  println!("Spawned thread has finished!");
}

نقل البيانات إلى الخيوط

عند استخدام متغيرات من البيئة داخل إغلاق spawn، تدخل قواعد الملكية في Rust حيّز التنفيذ. تحاول الإغلاقات افتراضيًا استعارة المتغيرات.

إذا تجاوز عمر الخيط المُنشأ نطاق الخيط الرئيسي الذي عُرّف فيه المتغير، فقد يؤدي ذلك إلى مرجع معلّق. ولإصلاح ذلك، استخدموا الكلمة المفتاحية move قبل معاملات الإغلاق.

  • تجبر move الإغلاق على تولّي ملكية المتغيرات التي التقطها.
  • ويضمن ذلك صلاحية البيانات طوال مدة حياة الخيط الجديد.
use std::thread;

fn main() {
  let data = String::from("Hello from outer scope");

  let handle = thread::spawn(move || { // Use 'move' to take ownership of 'data'
    println!("Data in thread: {}", data);
  });

  handle.join().unwrap();
  // println!("Data after thread: {}", data); // This would cause a compile error!
  println!("Main thread finished.");
}

التواصل بتمرير الرسائل

رغم أن مشاركة البيانات مباشرةً بين الخيوط (الحالة المشتركة) ممكنة في Rust، فإنها تتطلب مزامنة دقيقة، مثل استخدام Mutex وArc، اللذين تتناولونهما في دروس أخرى.

يُعد تمرير الرسائل نهجًا أكثر أمانًا وغالبًا أبسط للتزامن. إذ تتواصل الخيوط بإرسال الرسائل إلى بعضها بعضًا، متجنبةً الوصول المباشر إلى الذاكرة المشتركة.

  • يرسل أحد الخيوط البيانات.
  • يستقبل خيط آخر البيانات.
  • توفر مكتبة Rust القياسية std::sync::mpsc لهذا الغرض.

إعداد قناة `mpsc`

std::sync::mpsc اختصار لعبارة Multiple Producer, Single Consumer، أي «منتجون متعددون، ومستهلك واحد». وهذا يعني أن عدة خيوط يمكنها إرسال رسائل، لكن خيطًا واحدًا فقط يمكنه استقبالها.

لإنشاء قناة، تستدعي mpsc::channel()، التي تُرجع زوجًا يتكوّن من:

  • ‏Sender (tx): يُستخدم لإرسال الرسائل.
  • ‏Receiver (rx): يُستخدم لاستقبال الرسائل.

لنرَ كيفية إنشاء قناة وإرسال رسالة بسيطة.

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

fn main() {
  // Create a new channel
  let (tx, rx) = mpsc::channel();

  thread::spawn(move || {
    let val = String::from("hi");
    tx.send(val).unwrap(); // Send the message
    // println!("val is {}", val); // Error: val moved to tx.send()
  });

  // Receive the message in the main thread
  let received = rx.recv().unwrap();
  println!("Got: {}", received);
}

إرسال رسائل متعددة

يمكنك إرسال عدة رسائل عبر القناة نفسها. يحتوي Receiver على أساليب للتعامل مع ذلك:

  • rx.recv(): يحظر الخيط الحالي حتى يتم استقبال رسالة، ويُرجع قيمة من النوع Result.
  • rx.try_recv(): لا يحظر التنفيذ، بل يُرجع قيمة من النوع Result فورًا، إما مع رسالة أو مع خطأ إذا لم تتوفر أي رسالة.
  • يمكنك أيضًا التكرار مباشرةً على Receiver للحصول على الرسائل حتى يغلق المرسل.
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
  let (tx, rx) = mpsc::channel();

  thread::spawn(move || {
    let msgs = vec!["hi", "from", "the", "thread"];
    for msg in msgs {
      tx.send(String::from(msg)).unwrap();
      thread::sleep(Duration::from_millis(10));
    }
  });

  // Iterate over the receiver to get all messages
  for received in rx {
    println!("Got: {}", received);
  }
  println!("All messages received!");
}

منتجون متعددون، ومستهلك واحد

يشير الجزء «MP» في mpsc إلى Multiple Producers، أي «منتجون متعددون». يمكنك استنساخ Sender للسماح لعدة خيوط بإرسال رسائل إلى Receiver نفسه.

تشارك كل نسخة مستنسخة من Sender في الوصول إلى القناة نفسها، مما يتيح إرسال الرسائل بالتزامن من خيوط مختلفة.

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

fn main() {
  let (tx, rx) = mpsc::channel();

  let tx1 = tx.clone(); // Clone the sender for another thread
  thread::spawn(move || {
    tx1.send(String::from("hello from tx1")).unwrap();
  });

  thread::spawn(move || {
    tx.send(String::from("hello from tx2")).unwrap();
  });

  // Collect all messages
  let mut received_messages: Vec<String> = vec![];
  for received in rx {
    received_messages.push(received);
    if received_messages.len() == 2 { break; } // Assuming 2 messages for this example
  }
  println!("Received: {:?}", received_messages);
}

التزامن الآمن مع Rust

يُعد نظاما الملكية والأنواع في Rust أساسيين لتحقيق التزامن الآمن. فمن خلال فرض القواعد في وقت الترجمة، تمنع Rust أخطاء التزامن الشائعة مثل سباقات البيانات.

  • تمنع الملكية وجود مراجع قابلة للتعديل متعددة للبيانات نفسها.
  • تضمن الكلمة المفتاحية move نقل البيانات بأمان إلى خيط جديد.
  • يُلغي تمرير الرسائل (mpsc) الحاجة إلى الذاكرة المشتركة تمامًا، مما يجعل الاتصال آمنًا بطبيعته.

تتيح لك هذه الآليات كتابة تعليمات برمجية متزامنة بثقة، مع العلم بأن المصرّف سيتحقق من العديد من المشكلات المحتملة.

اختبار مفاهيم التزامن

أي العبارات التالية حول بدائيات التزامن في Rust صحيحة؟

مراجعة: الخيوط وتمرير الرسائل

أحسنت! لقد تعلمت أساسيات التزامن الآمن في Rust:

  • ننشىء خيوطًا جديدة باستخدام std::thread::spawn.
  • نستخدم JoinHandle::join() للانتظار حتى يكتمل خيط ما.
  • تنقل الكلمة المفتاحية move ملكية البيانات بأمان إلى داخل خيط.
  • يوفر std::sync::mpsc نظامًا متينًا لتمرير الرسائل بغرض الاتصال بين الخيوط.
  • تمنع قواعد الملكية في Rust سباقات البيانات في وقت الترجمة، مما يجعل البرمجة المتزامنة أكثر أمانًا.

يوفر هذا الدرس أساسًا متينًا لبناء تطبيقات سريعة الاستجابة وفعّالة. واصل التدريب!

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

هل درس «التزامن الآمن مع الخيوط» مجاني؟

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

ماذا ستتعلم في «التزامن الآمن مع الخيوط»؟

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

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

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

كم من الوقت يستغرق درس «التزامن الآمن مع الخيوط»؟

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

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

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

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

  1. المؤشرات الذكية Box وRc وArc
  2. القابلية الداخلية للتغيير: RefCell وCell
  3. التزامن الآمن مع الخيوط
← العودة إلى Learn Rust Coding