قفل القراء والكتاب

في علوم الحاسوب ، يُعدّ قفل القراءة والكتابة ( قفل كاتب واحد ، [ 1 ] قفل قارئ متعدد ، [ 2 ] قفل دفع ، [ 3 ] أو قفل MRSW ) أداةً أساسيةً للتزامن تُعالج إحدى مشكلات القراءة والكتابة . يسمح قفل القراءة والكتابة بالوصول المتزامن لعمليات القراءة فقط، بينما تتطلب عمليات الكتابة وصولًا حصريًا. هذا يعني أن عدة خيوط يمكنها قراءة البيانات بالتوازي، ولكن يلزم وجود قفل حصري لكتابة البيانات أو تعديلها. عندما يقوم كاتب بكتابة البيانات، يتم حظر جميع الكُتّاب والقراء الآخرين حتى ينتهي الكاتب من الكتابة. من الاستخدامات الشائعة لهذا القفل التحكم في الوصول إلى بنية بيانات في الذاكرة لا يمكن تحديثها بشكل ذري ، وتكون غير صالحة (ولا ينبغي قراءتها بواسطة خيط آخر) حتى يكتمل التحديث.

عادة ما يتم إنشاء أقفال القراءة والكتابة فوق mutexes ومتغيرات الحالة ، أو فوق semaphores .

قفل قابل للترقية

تسمح بعض أقفال القراءة والكتابة بترقية القفل بشكل ذري من وضع القراءة إلى وضع الكتابة، وكذلك تخفيضه من وضع الكتابة إلى وضع القراءة. [ 4 ] تُعد ترقية القفل من وضع القراءة إلى وضع الكتابة عرضةً لحالات الجمود، إذ عندما يحاول خيطان يحملان أقفال قراءة الترقية إلى أقفال كتابة، ينشأ جمود لا يمكن حله إلا بتحرير أحد الخيوط لقفل القراءة الخاص به. يمكن تجنب الجمود بالسماح لخيط واحد فقط بالحصول على القفل في "وضع القراءة بنية الترقية إلى وضع الكتابة" في حين لا توجد خيوط في وضع الكتابة، وربما توجد خيوط غير صفرية في وضع القراءة.

السياسات ذات الأولوية

يمكن تصميم أقفال القراءة والكتابة بسياسات أولوية مختلفة للوصول للقراءة والكتابة. يمكن تصميم القفل إما لإعطاء الأولوية دائمًا للقراء ( تفضيل القراءة )، أو لإعطاء الأولوية دائمًا للكتاب ( تفضيل الكتابة )، أو تركه دون تحديد للأولوية. تؤدي هذه السياسات إلى مفاضلات مختلفة فيما يتعلق بالتزامن وحالات الحرمان من الوصول .

  • تتيح أقفال RW ذات أولوية القراءة أقصى قدر من التزامن، ولكنها قد تؤدي إلى حرمان الكتابة إذا كان التنافس شديدًا. وذلك لأن خيوط الكتابة لن تتمكن من الحصول على القفل طالما أن خيط قراءة واحد على الأقل يحتفظ به. وبما أن عدة خيوط قراءة قد تحتفظ بالقفل في وقت واحد، فهذا يعني أن خيط الكتابة قد يستمر في انتظار القفل بينما تتمكن خيوط قراءة جديدة من الحصول عليه، حتى إلى الحد الذي قد يظل فيه الكاتب ينتظر بعد أن تُحرر جميع خيوط القراءة التي كانت تحتفظ بالقفل عند محاولته الأولى للحصول عليه القفل. قد تكون أولوية القراءة ضعيفة ، كما وُصف للتو، أو قوية ، مما يعني أنه كلما حرر الكاتب القفل، فإن أي قراء معطلين سيحصلون عليه تاليًا. [ 5 ] : 76
  • تتجنب أقفال القراءة والكتابة ذات الأولوية للكتابة مشكلة نقص الكاتب بمنع أي قارئ جديد من الحصول على القفل إذا كان هناك كاتب في قائمة الانتظار؛ إذ يحصل الكاتب على القفل بمجرد انتهاء جميع القراء الذين كانوا يحملونه بالفعل. [ 6 ] أما عيب هذه الأقفال فهو أنها تسمح بتزامن أقل في وجود خيوط الكتابة، مقارنةً بأقفال القراءة والكتابة ذات الأولوية للقراءة. كما أن أداء القفل أقل لأن كل عملية، سواءً للحصول على القفل أو تحريره للقراءة أو الكتابة، أكثر تعقيدًا، إذ تتطلب داخليًا الحصول على قفلين وتحريرهما بدلًا من واحد. يُعرف هذا النوع أحيانًا باسم قفل القراءة والكتابة "المتحيز للكتابة". [ 7 ]
  • لا توفر أقفال القراءة والكتابة ذات الأولوية غير المحددة أي ضمانات فيما يتعلق بالوصول للقراءة مقابل الكتابة. قد تكون الأولوية غير المحددة مفضلة في بعض الحالات إذا سمحت بتنفيذ أكثر كفاءة.

تطبيق

توجد عدة استراتيجيات لتنفيذ أقفال القراءة والكتابة، مما يقللها إلى بدائيات التزامن التي يُفترض أنها موجودة مسبقًا.

استخدام اثنين من الأقفال المتبادلة

يوضح راينال كيفية تنفيذ قفل القراءة/الكتابة باستخدام قفلين متبادلين وعداد عدد صحيح واحد. يتتبع العداد b عدد القراء المحظورين. يحمي أحد القفلين المتبادلين، r ، القفل b ويستخدمه القراء فقط؛ أما الآخر، g (اختصارًا لـ "عام") فيضمن الاستبعاد المتبادل للكتاب. يتطلب هذا أن يكون القفل المتبادل الذي حصل عليه أحد الخيوط قابلاً للتحرير بواسطة خيط آخر. فيما يلي رمز زائف للعمليات:

تهيئة

اضبط قيمة b على 0. تم فتح قفل r . تم فتح قفل g .

ابدأ القراءة

قفل r . زيادة ب . إذا كانت قيمة b تساوي 1 ، فقم بقفل g . افتح r .

نهاية القراءة

قفل r . انخفاض ب . إذا كانت قيمة b تساوي 0 ، فقم بفك قفل g . افتح r .

ابدأ الكتابة

قفل ز .

إنهاء الكتابة

افتح g .

هذا التطبيق يفضل القراءة. [ 5 ] : 76

باستخدام متغير شرطي و mutex

بدلاً من ذلك، يمكن تنفيذ قفل القراءة/الكتابة باستخدام متغير شرط ( cond )، وقفل عادي (mutex) ( g )، وعدادات وعلامات مختلفة تصف الخيوط النشطة أو المنتظرة حاليًا. [ 8 ] [ 9 ] [ 10 ] بالنسبة لقفل القراءة/الكتابة الذي يُفضل الكتابة، يمكن استخدام عدادين صحيحين وعلامة منطقية واحدة.

num_readers_active : عدد القراء الذين حصلوا على القفل (عدد صحيح)
num_writers_waiting : عدد الكُتّاب المنتظرين للوصول (عدد صحيح)
writer_active : ما إذا كان الكاتب قد حصل على القفل (Boolean).

في البداية ، تكون قيمتا num_readers_active و num_writers_waiting صفرًا، وتكون قيمة writer_active خاطئة.

يمكن تنفيذ عمليات القفل والتحرير على النحو التالي:

ابدأ القراءة

قفل g طالما أن عدد الكتاب المنتظرين أكبر من 0 أو أن الكاتب نشط : انتظر الشرط ، g [ أ ] زيادة عدد القراء النشطين فتح قفل g .

نهاية القراءة

قفل g إنقاص عدد القراء النشطين إذا كان عدد القراء النشطين = 0 : إشعار الشرط (بث) افتح g .

ابدأ الكتابة

قم بقفل g وزيادة عدد الكتاب المنتظرين طالما أن عدد القراء النشطين أكبر من 0 أو أن الكاتب نشط : انتظر الشرط ، g قلل عدد الكتاب المنتظرين ، اضبط الكاتب النشط على صحيح، افتح g .

إنهاء الكتابة

قفل  ضبط writer_active على false ، إخطار cond (بث) افتح g .

دعم لغات البرمجة

مثال بلغة Rust

استخدم std :: sync :: RwLock ؛let lock = RwLock :: new ( 5 );// يمكن الاحتفاظ بالعديد من أقفال القراءة في وقت واحد { let r1 = lock.read ( ). unwrap ( ) ; let r2 = lock.read (). unwrap ( ); assert_eq! ( * r1 , 5 ); assert_eq! ( * r2 , 5 ); } // يتم إسقاط أقفال القراءة عند هذه النقطة// لا يمكن الاحتفاظ إلا بقفل كتابة واحد، مع ذلك { let mut w = lock.write (). unwrap ( ) ; * w += 1 ; assert_eq! ( * w , 6 ); } // يتم إسقاط قفل الكتابة هنا

البدائل

تُعدّ خوارزمية القراءة والنسخ والتحديث (RCU) أحد حلول مشكلة القراء والكتاب. وتتميز هذه الخوارزمية بأنها لا تتطلب انتظارًا من جانب القراء. أما نواة لينكس، فتُطبّق حلاً خاصًا لعدد قليل من الكتاب يُسمى seqlock .

انظر أيضاً

ملحوظات

  1. هذه هي عملية "الانتظار" القياسية على متغيرات الشرط، والتي تقوم، من بين أمور أخرى، بتحرير mutex g .

مراجع

  1. هاميلتون، دوغ (21 أبريل 1995). "اقتراحات لقفل القراءة المتعددة/الكتابة الفردية؟" . مجموعة الأخبار : comp.os.ms-windows.nt.misc . يوزنت: hamilton.798430053@BIX.com . تاريخ الاسترجاع: 8 أكتوبر 2010 .  
  2. "حرية القفل العملية" بقلم كير فريزر 2004
  3. "أقفال الدفع - ما هي؟" . مدونة Ntdebugging . مدونات MSDN. 2 سبتمبر 2009. تم الاطلاع عليه بتاريخ 11 مايو 2017 .
  4. "التزامن § مفهوم UpgradeLockable – الامتداد" . مكتبات Boost C++ .
  5. 1 2 راينال، ميشيل (2012). البرمجة المتزامنة: الخوارزميات والمبادئ والأسس . سبرينغر.
  6. ستيفنز، دبليو. ريتشارد ؛ راغو، ستيفن أ. (2013). البرمجة المتقدمة في بيئة يونكس . أديسون-ويسلي. ص 409. 
  7. 1 2java.util.concurrent.locks.ReentrantReadWriteLock يوفر تطبيق قفل القراءة والكتابة في جافا وضعًا "عادلًا".
  8. هيرليهي، موريس؛ شافيت، نير (2012). فن برمجة المعالجات المتعددة . إلسيفير. ص 184-185 . 
  9. نيكولز، برادفورد؛ باتلار، ديك؛ فاريل، جاكلين (1996). برمجة PThreads: معيار POSIX لتحسين المعالجة المتعددة . أورايلي. الصفحات 84-89 . ISBN  9781565921153.
  10. بوتنهوف، ديفيد ر. (1997). البرمجة باستخدام خيوط POSIX . أديسون-ويسلي. ص 253-266 . 
  11. "مواصفات المجموعة المفتوحة الأساسية، الإصدار 6، معيار IEEE 1003.1، إصدار 2004: pthread_rwlock_destroy" . معهد مهندسي الكهرباء والإلكترونيات (IEEE) والمجموعة المفتوحة . تم الاطلاع عليه بتاريخ 14 مايو 2011 .
  12. java.util.concurrent.locks.ReadWriteLock
  13. "فئة ReaderWriteLockSlim (System.Threading)" . شركة مايكروسوفت . تم الاطلاع عليه بتاريخ 14 مايو 2011 .
  14. "ورقة بحثية جديدة معتمدة: N3659، التأمين المشترك في لغة C++ - هوارد هينانت، ديتليف فولمان، هانز بوهم" . مؤسسة C++ القياسية.
  15. أنتوني ويليامز. "المزامنة - بوست 1.52.0" . تم الاطلاع عليه بتاريخ 31 يناير 2012 .
  16. أليساندري، فيكتور (2015). برمجة تطبيقات الذاكرة المشتركة: مفاهيم واستراتيجيات في برمجة تطبيقات المعالجات متعددة النوى . مورغان كوفمان.
  17. "لغة برمجة Go - مزامنة الحزم" . تم الاطلاع عليه بتاريخ 30 مايو 2015 .
  18. "مزامنة القارئ والكاتب لأنظمة الوقت الحقيقي متعددة المعالجات ذات الذاكرة المشتركة" (PDF) .
  19. "std::sync::RwLock – Rust" . تم الاطلاع عليه بتاريخ 26 أكتوبر 2019 .
  20. "قفل القراءة/الكتابة لـ Twisted" . GitHub . تم الاطلاع عليه بتاريخ 28 سبتمبر 2016 .
  21. "أدوات التزامن في نواة لينكس: إشارات القراءة/الكتابة" . لينكس إنسايدرز . تم الاطلاع عليه بتاريخ 8 يونيو 2023 .