سؤال

أحاول منع سباقات البيانات في خادم متعدد الطوابق. مشكلتي هي ما يلي: هناك أ List<RServer>, ، نوع RServer هو فصل مع عدة حقول. الآن ، يحتوي الخادم على العديد من مؤشرات الترابط التي تعمل في نفس الوقت ويمكنها تعديل كلا من List (إضافة المزيد من العناصر) والفرد RServer مثيلات (تغيير الحقول).

لذا فإن استراتيجيتي هي صنع ملف readonly object RServerLock = new object( ) في كل من RServer مثيلات بالإضافة إلى أ readonly object RServerListLock = new object( ) وتوصل إلى كل الكود الذي يعدل إما ( List أو أ RServer مثيل) في أ lock. هل هذا آمن؟ ماذا يحدث إذا حاول خيط قفل أ RServerLock في حين واحد آخر هو قفلها؟

هل كانت مفيدة؟

المحلول

إذا كان لديك قفل منافس ، فيجب أن ينتظر الخيط الثاني حتى يصدر القفل الأول.

خطتك تبدو تقريبا حسنًا - لكنك تحتاج إلى قفل متى قراءة البيانات أيضًا ، للتأكد من حصولك على أحدث القيم ، والثابتة. وإلا فقد تكون في منتصف الطريق من خلال كتابة بعض القيم في موضوع واحد ، وترى بعض القيم الجديدة - ولكن ربما ليست كلها - والقيم القديمة ، كل ذلك في نفس الوقت في مؤشر ترابط مختلف.

إذا تمكنت من تجنب القيام بذلك قدر الإمكان ، فستكون حياتك أسهل :) الأنواع غير القابلة للتغيير تجعل الخيوط أكثر بساطة.

لا تنس أنه إذا كان لديك رمز من أي وقت مضى ستحتاج إلى قفلان في نفس الوقت (على سبيل المثال إضافة Rserver واحد وتعديل آخر ، ذري) أنت يجب تأكد من أنك دائماً Aquire Locks بنفس الترتيب - إذا حاول أحد الخيوط الحصول على Lock B أثناء حمل القفل A ، ويحاول خيط مختلف الحصول على Lock لفترة من الوقت ، فسوف ينتهي الأمر بـ Deadlock.

انظر بلدي ترابط البرنامج التعليمي أو جو ألباهاري لمزيد من التفاصيل. أيضًا ، إذا كنت مهتمًا بالتزامن ، فإن Joe Duffy لديه كتاب ممتاز الذي يخرج قريبًا جدًا.

نصائح أخرى

يبدو أن لديك مرشحًا رئيسيًا لمؤسسة ReaderWriterlock. أفضل فئة لاستخدامها (إذا كان وقت التشغيل الخاص بك يدعمه ، أعتقد أن 3.0+) هو readerWriterLockSlim لأن readerWriterlock الأصلي لديه مشكلات في الأداء.

واجه أحد مؤلفي مجلة MSDN أيضًا مشكلة في فصل RWLS ، ولن أخوض في التفاصيل هنا ، ولكن يمكنك النظر إليها هنا.

أعلم أن الكود التالي سوف يولد غضب المتطوعين القابل للتشويش ، لكنه في بعض الأحيان يجعل السكر النحوي لطيفًا. في أي حال ، قد تجد ما يلي مفيد:

    /// <summary>
    /// Opens the specified reader writer lock in read mode,
    /// specifying whether or not it may be upgraded.
    /// </summary>
    /// <param name="slim"></param>
    /// <param name="upgradeable"></param>
    /// <returns></returns>
    public static IDisposable Read(this ReaderWriterLockSlim slim, bool upgradeable)
    {
        return new ReaderWriterLockSlimController(slim, true, upgradeable);
    } // IDisposable Read

    /// <summary>
    /// Opens the specified reader writer lock in read mode,
    /// and does not allow upgrading.
    /// </summary>
    /// <param name="slim"></param>
    /// <returns></returns>
    public static IDisposable Read(this ReaderWriterLockSlim slim)
    {
        return new ReaderWriterLockSlimController(slim, true, false);
    } // IDisposable Read

    /// <summary>
    /// Opens the specified reader writer lock in write mode.
    /// </summary>
    /// <param name="slim"></param>
    /// <returns></returns>
    public static IDisposable Write(this ReaderWriterLockSlim slim)
    {
        return new ReaderWriterLockSlimController(slim, false, false);
    } // IDisposable Write

    private class ReaderWriterLockSlimController : IDisposable
    {
        #region Fields

        private bool _closed = false;
        private bool _read = false;
        private ReaderWriterLockSlim _slim;
        private bool _upgrade = false;

        #endregion Fields

        #region Constructors

        public ReaderWriterLockSlimController(ReaderWriterLockSlim slim, bool read, bool upgrade)
        {
            _slim = slim;
            _read = read;
            _upgrade = upgrade;

            if (_read)
            {
                if (upgrade)
                {
                    _slim.EnterUpgradeableReadLock();
                }
                else
                {
                    _slim.EnterReadLock();
                }
            }
            else
            {
                _slim.EnterWriteLock();
            }
        } //  ReaderWriterLockSlimController

        ~ReaderWriterLockSlimController()
        {
            Dispose();
        } //  ~ReaderWriterLockSlimController

        #endregion Constructors

        #region Methods

        public void Dispose()
        {
            if (_closed)
                return;
            _closed = true;

            if (_read)
            {
                if (_upgrade)
                {
                    _slim.ExitUpgradeableReadLock();
                }
                else
                {
                    _slim.ExitReadLock();
                }
            }
            else
            {
                _slim.ExitWriteLock();
            }

            GC.SuppressFinalize(this);
        } // void Dispose

        #endregion Methods
    } // Class ReaderWriterLockSlimController

ضع ذلك في فئة طريقة التمديد (فئة ثابتة عامة [الاسم]) واستخدمها على النحو التالي:

using(myReaderWriterLockSlim.Read())
{
  // Do read operations.
}

أو

using(myReaderWriterLockSlim.Read(true))
{
  // Read a flag.
  if(flag)
  {
    using(myReaderWriterLockSlim.Write()) // Because we said Read(true).
    {
      // Do read/write operations.
    }
  }
}

أو

using(myReaderWriterLockSlim.Write()) // This means you can also safely read.
{
  // Do read/write operations.
}

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

تبدو استراتيجيتك تبدو ، شريطة أن تحتاج إلى قفل كامل لمثيل Rserver. إذا تمكنت من قفل حقول مثيل RServer المحددة على وجه التحديد ، فقد يكون ذلك أكثر كفاءة. ومع ذلك ، فإنه سيزيد من عدد عمليات القفل وسيكون أكثر تعقيدًا.

هذا آمن. إذا اكتسب مؤشر ترابط واحد القفل ، فسيتعين على المواضيع الأخرى الانتظار حتى يتم إصدار القفل.

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

لإعادة صياغة بعض الشيء - يحتوي كل مثيل Rserver على متغير يسمى Rserverlock والذي سيتم قفله باستخدام كتلة قفل.

الموضوع 1 (T1) يأخذ قفلًا على Rserver 1 (R1). يأتي الموضوع 2 (T2) في محاولة لتعديل R1 الذي يتسبب في إدخال كتلة قفل R1. في هذه الحالة ، سينتظر T2 حتى ينتهي T1.

شيء واحد يجب أن تكون حذرًا حقًا هو عدد حالات RServer التي تنتهي بها الأمر. إذا انتهى بك الأمر مع عدد كبير من الحالات ، فأنت تحمل حوالي 20 بايت من البيانات فقط للقفل. في هذه المرحلة ، قد ترغب في التفكير في قطاع القفل.

إذا فهمت سؤالك بشكل صحيح ، يبدو أنك تحاول إنشاء عجلة حيث توجد عجلات جيدة تمامًا بالفعل.

تحقق من مساحة الاسم System.Threading في MSDN. يحتوي على الكثير من آليات القفل المصممة خصيصًا للتعامل مع مواقف مثل هذا.

http://msdn.microsoft.com/en-us/library/system.threading.aspx

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top