التزامن في Rust: الخيوط، الجوروتينات، async/await — تحليل كامل

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

التزامن في Rust: من الميوتكسات إلى async/await



عندما يتعلق الأمر بالأنظمة عالية الأداء، يجد Rust نفسه فورًا في مركز الاهتمام. ولكن ما الذي يجعله فريدًا حقًا؟ إنه التزامن في Rust — نهج تحصل فيه على سرعة C وأمان الذاكرة، دون التضحية بسهولة قراءة الكود. في هذه المقالة، سنقوم بتحليل ثلاث أدوات رئيسية: الخيوط الكلاسيكية، الجوروتينات الخفيفة (في سياق Rust — المهام غير المتزامنة)، و async/await الحديث. هيا بنا!



لماذا لا يخاف Rust من التزامن؟

على عكس العديد من اللغات، يحل Rust مشكلة سباقات البيانات على مستوى المترجم. نظام الملكية (ownership) والاقتراض (borrowing) يضمن أنك لا تستطيع عن طريق الخطأ تعديل البيانات من خيطين في وقت واحد. هذه ليست سحرًا — إنها فحص ثابت صارم. لهذا السبب يعتبر تعدد الخيوط في Rust من بين الأكثر أمانًا.



1. الخيوط (std::thread)

خيوط نظام التشغيل الكلاسيكية هي الأساس. في Rust، يتم تنفيذها من خلال وحدة std::thread. كل خيط هو عملية خفيفة منفصلة يمكن تنفيذها بالتوازي على نوى CPU مختلفة.



مثال: تشغيل الخيوط

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

fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("الخيط: {}", i); thread::sleep(Duration::from_millis(1)); } });

for i in 1..5 { println!("الخيط الرئيسي: {}", i); thread::sleep(Duration::from_millis(1)); }

handle.join().unwrap();}


هنا، thread::spawn ينشئ خيطًا جديدًا، و join ينتظر اكتماله. الأمر بسيط، ولكن هناك فارق بسيط: الخيوط «ثقيلة». يستهلك كل خيط ما لا يقل عن 2-8 ميجابايت من ذاكرة المكدس. هذا غير مناسب لآلاف المهام المتزامنة.



متى تستخدم الخيوط؟

  • عند الحاجة إلى توازٍ حقيقي (مهام مرتبطة بوحدة المعالجة المركزية CPU-bound).
  • عندما لا يتجاوز عدد الخيوط عدد النوى * 2.
  • عند العمل مع عمليات حظر (قراءة ملفات، استدعاءات متزامنة).


2. الجوروتينات في Rust: أسطورة أم حقيقة؟

بالمعنى الدقيق، لا توجد جوروتينات في Rust بنفس المعنى الموجود في Go. ولكن هناك مهام غير متزامنة (tasks) تتصرف تقريبًا بنفس الطريقة: فهي خفيفة الوزن، وتتبدل بشكل تعاوني (بدون مشاركة نظام التشغيل)، ويمكن تشغيلها بالآلاف على خيط واحد. لهذا، يتم استخدام وقت التشغيل Tokio أو async-std.



مثال: مهمة غير متزامنة (جوروتين) عبر Tokio

use tokio::task;

#[tokio::main]async fn main() { let handle = task::spawn(async { for i in 1..10 { println!("الجوروتين: {}", i); tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; } });

for i in 1..5 { println!("الرئيسي: {}", i); tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; }

handle.await.unwrap();}


لاحظ: task::spawn ينشئ مهمة خفيفة يتم تنفيذها ضمن مجموعة خيوط Tokio. تكلفة الذاكرة حوالي 2-3 كيلوبايت لكل مهمة. هذا يسمح بتشغيل 100,000 «جوروتين» دون مشاكل. على عكس Go، لا يوجد هنا جدولة مدمجة — أنت تختار وقت التشغيل بنفسك.



كيف تختلف جوروتينات Rust عن Go؟

  • في Rust — المهام تعاونية: هي نفسها تسلم التحكم عبر .await.
  • في Go — الجوروتينات استباقية: الجدولة نفسها تقاطعها عند نقاط الإدخال/الإخراج.
  • يمنح Rust تحكمًا أكبر: يمكنك اختيار وقت تشغيل أحادي الخيط أو متعدد الخيوط.


3. Async/await: قلب Rust غير المتزامن

الكلمات المفتاحية async و .await تحول الكود المتزامن إلى غير متزامن دون

المدونات

توصيات الكتب