سؤال

انا املك Dictionary<string, someobject>.

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

أحتاج إلى تحديث العناصر الموجودة في القاموس الخاص بي - مفتاح واحد في كل مرة وكنت أتساءل عما إذا كانت هناك أية مشكلات في استخدام القفل على قيمة .key لكائن القاموس الخاص بي؟

private static Dictionary<string, MatrixElement> matrixElements = new Dictionary<string, MatrixElement>();

//Pseudo-code

public static void UpdateValue(string key)
{
    KeyValuePair<string, MatrixElement> keyValuePair = matrixElements[key];
    lock (keyValuePair.Key)
    {
        keyValuePair.Value  = SomeMeanMethod();
    }
}

هل سيصمد ذلك في المحكمة أم يفشل؟أريد فقط أن يتم قفل كل قيمة في القاموس بشكل مستقل، لذا فإن قفل (وتحديث) إحدى القيم لا يؤدي إلى قفل القيم الأخرى.كما أنني أدرك أن القفل سيستمر لفترة طويلة - لكن البيانات ستكون غير صالحة حتى يتم تحديثها بالكامل.

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

المحلول

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

لو تقوم بتغليف القاموس بالكامل وإنشاء المفاتيح بنفسك (لا يتم تمريرها مطلقًا، فقد تكون آمنًا.

ومع ذلك، حاول الالتزام بقاعدة واحدة - الحد من رؤية الكائنات التي تقفلها على رمز القفل نفسه كلما أمكن ذلك.

لهذا السبب ترى هذا:

public class Something
{
  private readonly object lockObj = new object();

  public SomethingReentrant()
  {
    lock(lockObj)    // Line A
    {
      // ...
     }
   }
}

بدلاً من رؤية السطر A أعلاه تم استبداله بـ

  lock(this)

بهذه الطريقة، يتم قفل كائن منفصل، وتكون الرؤية محدودة.

يحرر جون سكيت لاحظ بشكل صحيح أن lockObj أعلاه يجب أن يكون للقراءة فقط.

نصائح أخرى

لا، هذا لن يجدي نفعا.

والسبب هو سلسلة interning . وهذا يعني أن:

string a = "Something";
string b = "Something";

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

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

class Something
{
    bool threadSafeBool = true;
    object threadSafeBoolLock = new object(); // Always lock this to use threadSafeBool
}

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

وPS. تغيير المجموعة التي تقوم بالتكرار عبر لا يعتبر لطيف جدا. وسوف رمي حتى استثناء مع معظم أنواع جمع. محاولة ريفاكتور هذا - على سبيل المثال أعاد على قائمة المفاتيح، إذا كان سوف يكون دائما ثابتة، وليس أزواج.

ملحوظة:أفترض أن الاستثناء عند تعديل المجموعة أثناء التكرار تم إصلاحه بالفعل

القاموس ليس مجموعة آمنة للخيط، مما يعني أنه كذلك لا آمن لتعديل وقراءة المجموعة من سلاسل مختلفة دون مزامنة خارجية.يعد Hashtable (كان؟) آمنًا لسيناريو الكاتب الواحد والعديد من القراء، لكن القاموس له بنية بيانات داخلية مختلفة ولا يرث هذا الضمان.

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

  1. قمت بقفل المفتاح والبدء في تعديل القاموس
  2. يحاول مؤشر ترابط آخر الحصول على قيمة للمفتاح الذي يقع في نفس المجموعة المقفلة.لا يحدث هذا فقط عندما تكون رموز التجزئة لكائنين متماثلة، ولكن بشكل متكرر أكثر عندما يكون رمز التجزئة٪tableSize هو نفسه.
  3. يصل كلا الخيطين إلى نفس المجموعة (قائمة المفاتيح المرتبطة بنفس قيمة رمز التجزئة٪ tableSize)

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

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

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

إذا لم أكن مخطئا، وكان القصد الأصلي لقفل على عنصر واحد، بدلا من تأمين القاموس كله (مثل قفل على مستوى الجدول مقابل تأمين مستوى صف في DB)

ولا يمكنك قفل على القاموس المفتاح مثل الكثيرين هنا أوضح.

ما يمكنك القيام به، هو الحفاظ على القاموس الداخلي للكائنات قفل، والذي يتوافق مع القاموس الفعلي. حتى عندما كنت تريد أن تكتب إلى YourDictionary [Key1]، فسوف قفل أولا على InternalLocksDictionary [Key1] - هكذا فقط موضوع واحد والكتابة إلى YourDictionary

و(ليست نظيفة جدا) سبيل المثال يمكن أن يكون <لأ href = "http://kenegozi.com/blog/2008/08/24/keylevel-locked-cache-real-life-implementation/" يختلط = "نوفولو noreferrer "> وجدت هنا .

وجاء للتو عبر هذا ويعتقد حصة هوية بعض رمز كتبته قبل سنوات قليلة حيث كنت بحاجة إلى القاموس على أساس مفتاح

 using (var lockObject = new Lock(hashedCacheID))
 {
    var lockedKey = lockObject.GetLock();
    //now do something with the dictionary
 }

والطبقة قفل

class Lock : IDisposable
    {
        private static readonly Dictionary<string, string> Lockedkeys = new Dictionary<string, string>();

        private static readonly object CritialLock = new object();

        private readonly string _key;
        private bool _isLocked;

        public Lock(string key)
        {
            _key = key;

            lock (CritialLock)
            {
                //if the dictionary doesnt contain the key add it
                if (!Lockedkeys.ContainsKey(key))
                {
                    Lockedkeys.Add(key, String.Copy(key)); //enusre that the two objects have different references
                }
            }
        }

        public string GetLock()
        {
            var key = Lockedkeys[_key];

            if (!_isLocked)
            {
                Monitor.Enter(key);
            }
            _isLocked = true;

            return key;
        }

        public void Dispose()
        {
            var key = Lockedkeys[_key];

            if (_isLocked)
            {
                Monitor.Exit(key);
            }
            _isLocked = false;
        }
    }

في المثال الخاص بك، لا يمكنك أن تفعل ما تريد القيام به!

سوف تحصل على System.InvalidOperationException مع رسالة تم تعديل المجموعة؛قد لا يتم تنفيذ عملية التعداد.

وهنا مثال للإثبات:

using System.Collections.Generic;
using System;

public class Test
{
    private Int32 age = 42;

    static public void Main()
    {
       (new Test()).TestMethod();
    }

    public void TestMethod()
    {
        Dictionary<Int32, string> myDict = new Dictionary<Int32, string>();

        myDict[age] = age.ToString();

        foreach(KeyValuePair<Int32, string> pair in myDict)
        {
            Console.WriteLine("{0} : {1}", pair.Key, pair.Value);
            ++age;
            Console.WriteLine("{0} : {1}", pair.Key, pair.Value);
            myDict[pair.Key] = "new";
            Console.WriteLine("Changed!");
        }
    }   
}

سيكون الإخراج:

42 : 42
42 : 42

Unhandled Exception: System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.Enumerator.MoveNext()
   at Test.TestMethod()
   at Test.Main()

أستطيع أن أرى بعض المشكلات المحتملة هناك:

  1. يمكن مشاركة السلاسل، لذلك لا تعرف بالضرورة من هو الآخر الذي قد يقوم بتأمين هذا الكائن الرئيسي لأي سبب آخر
  2. سلاسل قد لا أن تكون مشتركة:ربما تكون مقفلاً على مفتاح سلسلة واحد بالقيمة "Key1" وقد يحتوي جزء آخر من التعليمات البرمجية على كائن سلسلة مختلف يحتوي أيضًا على الأحرف "Key1".بالنسبة للقاموس، هما نفس المفتاح ولكن فيما يتعلق بالقفل، فهما كائنان مختلفان.
  3. لن يمنع هذا القفل إجراء تغييرات على كائنات القيمة نفسها، أي. matrixElements[someKey].ChangeAllYourContents()
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top