ضمان الوصول Thread-Safe
إلى أعضاء الـ Singleton في C#
في العديد من تطبيقات C#، يتم تنفيذ نمط الـ singleton بشكل متكرر لضمان أن يكون للصنف مثيل واحد فقط ويوفر نقطة وصول عالمية لذلك المثيل. ومع ذلك، عندما تصل خيوط متعددة إلى أعضاء الـ singleton، يثير ذلك مخاوف بشأن سلامة الخيوط. تتناول هذه المقالة هذه القضية، مع التركيز بشكل خاص على سيناريو شائع يتعلق بطريقة Toggle()
الخاصة بفئة الـ singleton وكيفية ضمان العمليات الآمنة من حيث الوصول داخلها.
فهم المشكلة
انظر إلى فئة الـ singleton التالية في C#:
public class MyClass
{
private static readonly MyClass instance = new MyClass();
public static MyClass Instance
{
get { return instance; }
}
private int value = 0;
public int Toggle()
{
if(value == 0)
{
value = 1;
}
else if(value == 1)
{
value = 0;
}
return value;
}
}
تم تصميم طريقة Toggle()
هنا لتبديل value
بين 0 و 1. ومع ذلك، إذا استدعت عدة خيوط Toggle()
في نفس الوقت، فإن الطريقة ليست آمنة من حيث الخيوط. ونتيجة لذلك، يمكن أن يحدث سلوك غير متوقع ونتائج غير صحيحة.
لماذا ليست Toggle()
آمنة من حيث الخيوط؟
عندما تصل خيطان إلى Toggle()
في نفس الوقت، يمكن أن يتداخل تنفيذها بطريقة تجعل كلا الخيطين يقرأان ويعدلان نفس متغير value
. يمكن أن تؤدي هذه الحالة إلى سيناريوهات حيث لا يحدث الناتج المتوقع. على سبيل المثال، يمكن أن يقوم كلا الخيطين بتقييم value
في نفس الوقت، مما يؤدي إلى حالة سباق.
مثال على حالة سباق
// تنفيذ الخيط 1
if(value == 0)
{
value = 1;
// يتدخل الخيط 2 الآن
// يقوم الخيط 2 بتقييم value كـ 1 ويعدله إلى 0
}
// يرجع الخيط 1 0، وهو ليس القيمة المتوقع.
كيفية جعل Toggle()
آمنة من حيث الخيوط
مبدأ القفل
لتحقيق سلامة الخيط، نحتاج إلى ضمان أنه عند تنفيذ خيط لقسم حرج من التعليمات البرمجية (في هذه الحالة، تعديل value
)، لا يمكن لخيط آخر التدخل. في C#، يمكننا تنفيذ ذلك باستخدام جملة lock
.
خطوة بخطوة لحل المشكلة
-
تحديد الأقسام الحرجة: حدد أي أقسام من التعليمات البرمجية تحتاج إلى التنفيذ دون انقطاع. في هذه الحالة، قراءة وكتابة
value
. -
إنشاء كائن قفل: نظرًا لأن
value
هو نوع قيمة (int)، فإنه لا يمكن قفله. لذلك، نحتاج إلى كائن منفصل من أجل القفل.
private static readonly object locker = new object();
- لف التعليمات البرمجية في قفل: استخدم جملة
lock
لإنشاء قسم حرج لتعديلvalue
.
طريقة Toggle المعدلة
إليك كيف ستبدو طريقة Toggle()
بعد تنفيذ الحماية من الخيوط:
public int Toggle()
{
lock (locker)
{
if(value == 0)
{
value = 1;
}
else if(value == 1)
{
value = 0;
}
return value;
}
}
النقاط الرئيسية
- آلية القفل: من خلال القفل على كائن مخصص، تتأكد من أن خيط واحد فقط يمكنه الوصول إلى القسم الحرج في أي وقت.
- تجنب قفل أنواع القيمة: تذكر دائمًا أنه يمكنك قفل أنواع المرجع فقط، ومن هنا تأتي حاجة كائن
locker
. - اكتمال التنفيذ: دائمًا راجع وحدد أي أجزاء من التعليمات البرمجية قد تتأثر بالوصول المتزامن لتجنب حالات السباق.
الخاتمة
يعد جعل صف الـ singleton الخاص بك آمنًا من حيث الخيوط أمرًا حيويًا في بيئة متعددة الخيوط لمنع سلوك غير متوقع. من خلال استخدام الأقفال، يمكنك حماية الموارد المشتركة بشكل فعال. يمكن أن يكون نمط الـ singleton، عند تنفيذه بشكل صحيح مع اعتبارات لسلامة الخيوط، قويًا في الحفاظ على استقرار التطبيق وصحته.
في النهاية، يصبح فهم سلامة الخيوط في الـ singleton الخاص بك مهارة أساسية لأي مطور C# يعمل على تطبيقات معقدة.