سؤال

لم أتمكن من العثور على معلومات كافية ConcurrentDictionary الأنواع ، لذلك اعتقدت أنني سأسأل عنها هنا.

حاليا ، أنا استخدم Dictionary لعقد جميع المستخدمين الذين يتم الوصول إليه باستمرار بواسطة مؤشرات ترابط متعددة (من تجمع مؤشرات الترابط ، لذلك لا يوجد كمية دقيقة من مؤشرات الترابط) ، ولديه وصول متزامن.

لقد اكتشفت مؤخرًا أن هناك مجموعة من مجموعات آمنة الخيوط في .NET 4.0 ، ويبدو أنها ممتعة للغاية. كنت أتساءل ، ما الذي سيكون خيار "أكثر كفاءة وأسهل في الإدارة" ، حيث لدي الخيار بين الحصول على طبيعية Dictionary مع وصول متزامن ، أو لديك ConcurrentDictionary وهو بالفعل آمن الخيط.

الإشارة إلى .NET 4.0's ConcurrentDictionary

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

المحلول

يمكن النظر إلى مجموعة آمنة من الخيوط مقابل جمعية غير تراديس آافية بطريقة مختلفة.

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

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

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

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

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

ومع ذلك ، لا تضمن مجموعة Threadsafe أن العمليات المتسلسلة على مؤشر الترابط جميعها تعمل على نفس "اللقطة" من بنية البيانات الداخلية الخاصة بها ، مما يعني أنه إذا كان لديك رمز مثل هذا:

if (tree.Count > 0)
    Debug.WriteLine(tree.First().ToString());

قد تحصل على nullreferenceexception لأنه بين بين tree.Count و tree.First(), ، قام خيط آخر بمسح العقد المتبقية في الشجرة ، مما يعني First() سيعود null.

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

نصائح أخرى

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

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

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

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

أود أن أقول ، فقط استخدم concurrentdictionary.

أعتقد أن طريقة concurrentdiction.getoradd هي بالضبط ما تحتاجه السيناريوهات متعددة الخيوط.

هل رايت ال الامتدادات التفاعلية لـ .NET 3.5SP1. وفقًا لجون سكيت ، قاموا بإنشاء حزمة من الامتدادات المتوازية وهياكل البيانات المتزامنة لـ .NET3.5 SP1.

هناك مجموعة من العينات لـ .NET 4 Beta 2 ، والتي تصف بتفاصيل جيدة حول كيفية استخدامها ملحقات متوازية.

لقد قضيت للتو الأسبوع الماضي في اختبار ConcurrentDictionary باستخدام 32 موضوعًا لأداء I/O. يبدو أنه يعمل على النحو المعلن ، مما يشير إلى وجود قدر هائل من الاختبار.

تعديل: .net 4 concurrentdictionary والأنماط.

أصدرت Microsoft PDF تسمى أنماط برمجة Paralell. إنه يستحق التنزيل بشكل حقيقي كما هو موضح في التفاصيل الجميلة حقًا الأنماط الصحيحة التي يجب استخدامها في ملحقات .NET 4 المتزامنة والأنماط المضادة لتجنبها. ها هو.

في الأساس تريد الذهاب مع ConvintDictionary الجديد. مباشرة خارج المربع ، عليك كتابة كود أقل لجعل برامج مؤشر ترابط آمنة.

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

وجدنا ذلك تغييره إلىReadOnlyDictionaryتحسين الأداء العام.

ال ConcurrentDictionary هو خيار رائع إذا كان يفي الكل احتياجات سلامة الموضوع الخاصة بك. إذا لم يكن الأمر كذلك ، وبعبارة أخرى منكم تفعل أي شيء معقد قليلاً ، طبيعية Dictionary+lock قد يكون خيارًا أفضل. على سبيل المثال ، دعنا نقول إنك تقوم بإضافة بعض الطلبات إلى قاموس ، وتريد الحفاظ على تحديث المبلغ الإجمالي للطلبات. يمكنك كتابة رمز مثل هذا:

private ConcurrentDictionary<int, Order> _dict;
private Decimal _totalAmount = 0;

while (await enumerator.MoveNextAsync())
{
    Order order = enumerator.Current;
    _dict.TryAdd(order.Id, order);
    _totalAmount += order.Amount;
}

هذا الرمز ليس مؤشر ترابط. خيوط متعددة تحديث _totalAmount قد يترك الحقل في حالة تالفة. لذلك قد تحاول حمايته بـ lock:

_dict.TryAdd(order.Id, order);
lock (_locker) _totalAmount += order.Amount;

هذا الرمز "أكثر أمانًا" ، لكنه لا يزال غير آمن. ليس هناك ما يضمن أن _totalAmount يتفق مع الإدخالات في القاموس. قد يحاول موضوع آخر قراءة هذه القيم ، لتحديث عنصر واجهة المستخدم:

Decimal totalAmount;
lock (_locker) totalAmount = _totalAmount;
UpdateUI(_dict.Count, totalAmount);

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

// Thread A
lock (_locker)
{
    _dict.TryAdd(order.Id, order);
    _totalAmount += order.Amount;
}

// Thread B
int ordersCount;
Decimal totalAmount;
lock (_locker)
{
    ordersCount = _dict.Count;
    totalAmount = _totalAmount;
}
UpdateUI(ordersCount, totalAmount);

هذا الرمز آمن تمامًا ، ولكن كل فوائد استخدام أ ConcurrentDictionary رحلو.

  1. أصبح الأداء أسوأ من استخدام طبيعية Dictionary, ، لأن القفل الداخلي داخل ConcurrentDictionary هو الآن مضيعة ومتكررة.
  2. يجب مراجعة جميع التعليمات البرمجية الخاصة بك للاستخدامات غير المحمية للمتغيرات المشتركة.
  3. أنت عالق باستخدام واجهة برمجة تطبيقات محرجة (TryAdd?, AddOrUpdate?).

لذا فإن نصيحتي هي: ابدأ بـ Dictionary+lock, ، والحفاظ على خيار الترقية لاحقًا إلى أ ConcurrentDictionary كتحسين الأداء ، إذا كان هذا الخيار قابلاً للتطبيق بالفعل. في كثير من الحالات لن يكون.

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