تزامن الحالة المشتركة في لغة رست وتوسيع التزامن باستخدام Send و Sync
تُعتبر لغة رست (Rust) من أبرز لغات البرمجة الحديثة التي ركزت على الأمان في التعامل مع الذاكرة والتزامن بين الخيوط (Threads) بشكل صارم وفعّال. من أهم التحديات التي تواجهها لغات البرمجة متعددة الخيوط هو تزامن الحالة المشتركة (Shared-State Concurrency)، أي القدرة على مشاركة البيانات بين خيوط مختلفة بطريقة صحيحة وآمنة دون التسبب في تعارضات أو حالات سباق (Race Conditions). في هذا المقال سيتم تناول مفهوم تزامن الحالة المشتركة في رست، الأدوات التي توفرها اللغة لهذا الغرض، بالإضافة إلى توسيع مفهوم التزامن باستخدام الصفات الخاصة Send و Sync، والتي تمثل الركائز الأساسية لنظام الأمان في التزامن داخل رست.
مفهوم تزامن الحالة المشتركة (Shared-State Concurrency)
عند بناء تطبيقات تعمل على عدة خيوط تنفيذ (Threads)، يصبح من الضروري أحيانًا أن تتشارك هذه الخيوط بيانات أو حالة معينة. هذا التشارك يحتاج إلى آليات تسمح بخلو البيانات من حالات السباق أو التعارضات الناتجة عن وصول خيوط متعددة لنفس البيانات والتعديل عليها في نفس الوقت. مشكلة تزامن الحالة المشتركة تكمن في إمكانية الوصول المتزامن إلى نفس البيانات، مما قد يؤدي إلى نتائج غير متوقعة أو فساد في البيانات.
تتطلب إدارة هذه المشكلة في أي لغة برمجة استخدام آليات مثل الأقفال (Locks)، الحواجز (Barriers)، أو التراخيص (Permissions) التي تحكم الوصول إلى الحالة المشتركة. ما يميز رست هو اعتمادها على نظام ملكية صارم في وقت الترجمة (compile time) يمنع أخطاء التزامن الشائعة، ويُعزز الأمان.
كيفية التعامل مع الحالة المشتركة في رست
تستند رست إلى مبدأين رئيسيين لإدارة البيانات بين الخيوط:
-
الملكية (Ownership): تضمن رست أن كل قيمة لها مالك واحد فقط في أي وقت.
-
الاقتراض (Borrowing): تسمح لقيم أن تُقترض كمرجع دون نقل ملكيتها، مع ضمان عدم وجود تعارض في الاقتراض (لا يمكن أن يكون هناك اقتراض متغير ومتزامن في نفس الوقت).
لكن في حالة مشاركة الحالة بين خيوط متعددة، يصبح من الضروري تجاوز بعض القيود أو استخدام أدوات إضافية تتيح مشاركة آمنة بين الخيوط.
1. استخدام الأقفال (Mutex)
من أكثر الأدوات شيوعًا هو استخدام Mutex (اختصار لكلمة “mutual exclusion”)، حيث يوفر قفلًا يمنع خيطًا آخر من الوصول إلى البيانات أثناء استخدام خيط واحد لها.
مثال على استخدام Mutex في رست:
rustuse std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Counter: {}", *counter.lock().unwrap());
}
في هذا المثال:
-
Mutexيضمن أن تعديل المتغيرcounterيتم بطريقة متسلسلة. -
Arc(Atomic Reference Counting) يتيح مشاركة المراجع بين الخيوط بطريقة آمنة.
2. استخدام أنواع ذكية أخرى
-
RwLock: يسمح بقراءة متعددة متزامنة، أو كتابة واحدة فقط. -
Atomic types: تستخدم للعمليات التي يمكن أن تتم بدون قفل مثل زيادة قيمة عدد صحيح بطريقة ذرية.
دور الصفات Send و Sync في توسيع التزامن
مفهوم Send و Sync
في رست، الصفات (Traits) Send و Sync تُستخدم لتعريف أنواع البيانات التي يمكن نقلها أو مشاركتها بأمان بين الخيوط.
-
Send: تعني أن النوع يمكن نقله (moved) من خيط إلى آخر. أي أن الملكية يمكن نقلها بين الخيوط بأمان.
-
Sync: تعني أن النوع يمكن مشاركته (referenced) من عدة خيوط في نفس الوقت. إذا كان نوع ما يحقق الصفة
Sync، فإن الإشارة إليه كمرجع&Tيمكن أن تُشارك بأمان عبر خيوط متعددة.
لماذا هاتان الصفتان مهمتان؟
في رست، يتم فرض قيود صارمة على أنواع البيانات التي يتم تمريرها أو مشاركتها بين الخيوط لضمان الأمان. بشكل افتراضي:
-
كل أنواع البيانات التي لا تحتوي على مؤشرات غير آمنة (raw pointers) أو أنواع غير متزامنة تملك صفة
Send. -
معظم الأنواع التي يمكن أن تُقرأ أو تُقرا عبر مراجع متزامنة تملك صفة
Sync.
إذا حاولت تمرير نوع غير متزامن عبر خيوط أو مشاركته، سيقوم المترجم برفض الكود أثناء الترجمة.
علاقة Send و Sync بالأمان في التزامن
-
صفة
Sendتسمح بنقل ملكية النوع بين الخيوط، هذا يعني أن النوع لا يحتوي على أي حالة مشتركة غير محمية. -
صفة
Syncتسمح لمراجع متعددة للنوع أن تتواجد في خيوط مختلفة في نفس الوقت بدون التعارض، أي أن الوصول للبيانات يتم بطريقة متزامنة وآمنة.
لذلك، أي نوع يحتوي على بيانات يمكن أن تُعدل عبر مراجع متزامنة يجب أن يضمن آليات التزامن داخليًا مثل الأقفال لضمان صحة الصفة Sync.
تفاصيل تقنية حول Send و Sync
1. صفة Send
تعريفها في رست بسيط، وهي تعني أن نوع ما يمكن نقله بأمان بين الخيوط. معظم أنواع البيانات الأساسية في رست تملك الصفة Send تلقائيًا. الأنواع التي تحتوي على مؤشرات غير آمنة، أو تملك مراجع غير آمنة عادة لا تكون Send.
مثال: نوع Vec إذا كان T هو Send، فإن Vec أيضًا Send.
2. صفة Sync
تعني أن النوع يمكن استخدام مراجع &T له من خيوط متعددة دون خطر على سلامة البيانات. أنواع البيانات التي تستخدم أقفال داخلية مثل Mutex تملك صفة Sync حتى لو كان T غير Sync، لأن Mutex يتحكم في الوصول.
كيف توضح رست التزامن باستخدام هذه الصفات؟
في الواقع، كل العمليات المتعلقة بالتزامن تتطلب أن تتحقق هذه الصفات في النوع المستخدم. عند محاولة مشاركة أو تمرير نوع ما بين خيوط، يقوم المترجم بالتحقق من:
-
هل النوع يحقق صفة
Sendلنقله بين خيوط؟ -
هل النوع يحقق صفة
Syncللسماح بالوصول المتزامن للبيانات؟
هذه الميزة تسمح برصد المشاكل المتعلقة بالتزامن في وقت الترجمة، قبل أن تتحول إلى أخطاء في وقت التشغيل.
مثال عملي يوضح دور Send و Sync
rustuse std::thread;
struct NotSend {
data: *const i32, // مؤشر غير آمن
}
unsafe impl Sync for NotSend {} // جعلنا النوع Sync يدويًا
fn main() {
let not_send = NotSend { data: &10 };
// محاولة نقل النوع عبر خيط سيؤدي إلى خطأ لأن NotSend ليس Send
// let handle = thread::spawn(move || {
// println!("{:?}", not_send.data);
// });
// handle.join().unwrap();
}
في المثال السابق، النوع NotSend يحتوي على مؤشر غير آمن ولا يحقق الصفة Send، لذلك لا يمكن نقله عبر خيوط، مما يمنع مشاكل محتملة في وقت التنفيذ.
الأدوات والأنماط التي تستخدم Send و Sync في المكتبة القياسية
1. Arc (Atomic Reference Counting)
Arc يستخدم في مشاركة البيانات بين خيوط متعددة. لكي يعمل Arc، يجب أن يكون T من النوع Sync، لأن المرجع &T يمكن مشاركته بأمان.
2. Mutex و RwLock
Mutex و RwLock تتيح الوصول المتزامن إلى البيانات المحمية. هذه الأنواع تحقق عادة Send و Sync عندما يكون T قابلًا للنقل أو المشاركة بشكل مناسب.
جدول يوضح العلاقة بين Send و Sync وأنواع الاستخدام
| الحالة | هل يحتاج Send؟ |
هل يحتاج Sync؟ |
أمثلة على الأنواع | ملاحظات |
|---|---|---|---|---|
| نقل الملكية بين خيوط | نعم | لا | أنواع البيانات الأولية، Vec |
يجب أن يكون T أيضًا Send |
| مشاركة المرجع بين خيوط | لا | نعم | Arc, Mutex |
T يجب أن يكون Sync |
| الوصول المتزامن المحمي بواسطة أقفال | نعم | نعم | Mutex, RwLock |
توفر التزامن الداخلي لتلبية متطلبات Send و Sync |
| أنواع تحتوي مؤشرات غير آمنة | لا | لا | مؤشرات خام (Raw pointers) | غير آمنة للمشاركة أو النقل بين الخيوط |
كيفية كتابة أنواع قابلة لـ Send و Sync
يمكن للمبرمج تعريف أنواع بيانات خاصة به وتحديد إذا ما كانت تدعم Send و Sync، ولكن يجب أن يكون على دراية بمخاطر التزامن. بشكل عام، يتم استنتاج هذه الصفات تلقائيًا بناءً على مكونات النوع. وإذا دعت الحاجة، يمكن تنفيذها يدويًا باستخدام unsafe impl مع تحذير شديد بعدم كسر سلامة الذاكرة.
الخلاصة
تزامن الحالة المشتركة في رست يتم عبر نظام صارم يعتمد على نظام الملكية والاقتراض، مدعومًا بصفات Send و Sync اللتين تضبطان انتقال ومشاركة البيانات بين الخيوط بأمان. هذه الصفات ليست فقط أدوات تصنيف، بل تشكل الأساس لمنع أخطاء التزامن في وقت الترجمة، مما يمنح رست ميزة تنافسية في مجال البرمجة المتزامنة.
التحكم في تزامن الحالة المشتركة باستخدام هذه المفاهيم يسمح ببناء تطبيقات متعددة الخيوط تكون آمنة، سريعة، وقابلة للصيانة، حيث أن رست تتجنب المشاكل التقليدية المتعلقة بالتزامن مثل حالات السباق وتعطل الذاكرة.
المصادر والمراجع
-
The Rust Programming Language, Steve Klabnik and Carol Nichols, 2019.
-
Official Rust Documentation: Concurrency in Rust
-
Rust Standard Library Documentation: Send and Sync Traits

