سؤال

لقد تمكنت من تنفيذ قاموس آمن لمؤشر الترابط في C# عن طريق الاشتقاق من IDictionary وتحديد كائن SyncRoot خاص:

public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
    private readonly object syncRoot = new object();
    private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();

    public object SyncRoot
    {
        get { return syncRoot; }
    } 

    public void Add(TKey key, TValue value)
    {
        lock (syncRoot)
        {
            d.Add(key, value);
        }
    }

    // more IDictionary members...
}

أقوم بعد ذلك بقفل كائن SyncRoot هذا عبر المستهلكين (خيوط متعددة):

مثال:

lock (m_MySharedDictionary.SyncRoot)
{
    m_MySharedDictionary.Add(...);
}

لقد تمكنت من إنجاح الأمر، لكن هذا أدى إلى بعض التعليمات البرمجية القبيحة.سؤالي هو، هل هناك طريقة أفضل وأكثر أناقة لتنفيذ قاموس آمن للسلاسل؟

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

المحلول

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

public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
    private readonly object syncRoot = new object();
    private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();

    public void Add(TKey key, TValue value)
    {
        lock (syncRoot)
        {
            d.Add(key, value);
        }
        OnItemAdded(EventArgs.Empty);
    }

    public event EventHandler ItemAdded;

    protected virtual void OnItemAdded(EventArgs e)
    {
        EventHandler handler = ItemAdded;
        if (handler != null)
            handler(this, e);
    }

    // more IDictionary members...
}

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

نصائح أخرى

ويدعى فئة. NET 4.0 التي تدعم التزامن ConcurrentDictionary .

من المؤكد تقريبًا أن محاولة المزامنة داخليًا لن تكون كافية لأنها عند مستوى منخفض جدًا من التجريد.لنفترض أنك قمت بـ Add و ContainsKey العمليات بشكل فردي آمن على النحو التالي:

public void Add(TKey key, TValue value)
{
    lock (this.syncRoot)
    {
        this.innerDictionary.Add(key, value);
    }
}

public bool ContainsKey(TKey key)
{
    lock (this.syncRoot)
    {
        return this.innerDictionary.ContainsKey(key);
    }
}

إذن ماذا يحدث عند استدعاء جزء التعليمات البرمجية هذا الذي يُفترض أنه آمن لسلسلة الرسائل من عدة سلاسل رسائل؟هل ستعمل دائمًا بشكل جيد؟

if (!mySafeDictionary.ContainsKey(someKey))
{
    mySafeDictionary.Add(someKey, someValue);
}

الجواب البسيط هو لا.في مرحلة ما Add ستطرح الطريقة استثناءً يشير إلى أن المفتاح موجود بالفعل في القاموس.قد تتساءل كيف يمكن أن يكون هذا باستخدام قاموس آمن لمؤشر الترابط؟حسنًا، نظرًا لأن كل عملية آمنة لمؤشر الترابط، فإن الجمع بين عمليتين ليس كذلك، حيث يمكن لمؤشر ترابط آخر تعديله بين مكالمتك لـ ContainsKey و Add.

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

lock (mySafeDictionary)
{
    if (!mySafeDictionary.ContainsKey(someKey))
    {
        mySafeDictionary.Add(someKey, someValue);
    }
}

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

  1. استخدم عادي Dictionary<TKey, TValue> ومزامنتها خارجيًا، مع تضمين العمليات المركبة عليها، أو

  2. اكتب غلافًا جديدًا آمنًا لمؤشر الترابط بواجهة مختلفة (على سبيل المثال.لا IDictionary<T>) الذي يجمع بين العمليات مثل AddIfNotContained الطريقة بحيث لا تحتاج أبدًا إلى دمج العمليات منه.

(أميل إلى الذهاب مع رقم 1 بنفسي)

ويجب عدم نشر الكائن القفل الخاص بك من خلال خاصية. يجب أن تكون موجودة الكائن قفل خاص لغرض وحيد من بوصفها نقطة التقاء.

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

هناك العديد من المشاكل في طريقة التنفيذ التي تصفها.

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

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

لا تحتاج إلى قفل خاصية SyncRoot في كائنات المستهلك الخاصة بك.القفل الموجود ضمن أساليب القاموس كافٍ.

للتوضيح:ما يحدث في النهاية هو أن قاموسك مغلق لفترة زمنية أطول من اللازم.

ما يحدث في حالتك هو ما يلي:

لنفترض أن الخيط A يكتسب القفل على SyncRoot قبل استدعاء m_mySharedDictionary.Add.يحاول مؤشر الترابط B بعد ذلك الحصول على القفل ولكن تم حظره.في الواقع، يتم حظر كافة المواضيع الأخرى.يُسمح لمؤشر الترابط A بالاتصال بأسلوب الإضافة.في بيان القفل ضمن طريقة الإضافة، يُسمح للخيط A بالحصول على القفل مرة أخرى لأنه يمتلكه بالفعل.عند الخروج من سياق القفل داخل الطريقة ثم خارج الطريقة، قام الخيط A بتحرير جميع الأقفال مما يسمح للخيوط الأخرى بالاستمرار.

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

ومجرد التفكير لماذا لا إعادة إنشاء القاموس؟ إذا القراءة هي عدد وافر من الكتابة ثم قفل ومزامنة كافة الطلبات.

مثال

    private static readonly object Lock = new object();
    private static Dictionary<string, string> _dict = new Dictionary<string, string>();

    private string Fetch(string key)
    {
        lock (Lock)
        {
            string returnValue;
            if (_dict.TryGetValue(key, out returnValue))
                return returnValue;

            returnValue = "find the new value";
            _dict = new Dictionary<string, string>(_dict) { { key, returnValue } };

            return returnValue;
        }
    }

    public string GetValue(key)
    {
        string returnValue;

        return _dict.TryGetValue(key, out returnValue)? returnValue : Fetch(key);
    }
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top