هو قفل المطلوبة مع كسول التهيئة على عميق ثابتة النوع ؟

StackOverflow https://stackoverflow.com/questions/652195

سؤال

إذا كان لدي بعمق ثابتة نوع (جميع الأعضاء للقراءة فقط إذا كانت نوع مرجع أعضاء ، ثم أنها أيضا تشير إلى الأشياء التي هي بعمق ثابتة).

أود أن تنفيذ كسول تهيئة مكان الإقامة على نوع من هذا القبيل:

private ReadOnlyCollection<SomeImmutableType> m_PropName = null;
public ReadOnlyCollection<SomeImmutableType> PropName
{
    get
    {
        if(null == m_PropName)
        {
            ReadOnlyCollection<SomeImmutableType> temp = /* do lazy init */;
            m_PropName = temp;
        }
        return m_PropName;
    }
}

من ما أستطيع أن أقول:

m_PropName = temp; 

...هو threadsafe.أنا لست قلقا كثيرا عن اثنين من المواضيع كل سباق إلى تهيئة في نفس الوقت, لأنها سوف تكون نادرة ، سواء أن تكون النتائج متطابقة من منظور منطقي و أنا أفضل عدم استخدام قفل لو لم يكن لديك إلى.

هل هذا العمل ؟ ما هي إيجابيات وسلبيات ؟

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

(الإجابات تشير إلى أن وقت التشغيل لن نسمح لهذا أن يحدث.)

تحرير: لذا قررت أن أذهب مع متشابكة CompareExchange طريقة مستوحاة من هذه المقالة عن جو دافي.

في الأساس:

private ReadOnlyCollection<SomeImmutableType> m_PropName = null;
public ReadOnlyCollection<SomeImmutableType> PropName
{
    get
    {
        if(null == m_PropName)
        {
            ReadOnlyCollection<SomeImmutableType> temp = /* do lazy init */;
            System.Threading.Interlocked(ref m_PropName, temp, null);
        }
        return m_PropName;
    }
}

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

كما ذكر في بعض التعليقات أدناه, هذا يعتمد على .NET 2.0 نموذج الذاكرة للعمل.وإلا m_PropName يجب أن تعلن المتقلبة.

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

المحلول

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

جون السكيت كبيرة الصفحة على تنفيذ singeltons في C#.

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

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

نصائح أخرى

يجب عليك استخدام القفل.وإلا هل خطر مثيلين من m_PropName القائمة في مختلف المواضيع.هذا قد لا يكون مشكلة في كثير من الحالات ؛ ومع ذلك, إذا كنت تريد أن تكون قادرة على استخدام == بدلا من .equals() ثم وهذا سوف يكون مشكلة.سباق نادر لا تكون الظروف أفضل الخلل لديهم.فهي من الصعب تصحيح وإعادة.

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

واحدة من أهم فوائد ثابتة الكائنات التي == ما يعادل .equals(), يسمح استخدام أكثر performant == وعلى سبيل المقارنة.إذا كنت لا مزامنة في كسول التهيئة ، ثم هل خطر فقدان هذا الاستحقاق.

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

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

تحتاج إلى جعل هذا المجال volatile على الرغم من.

بخصوص هذا:

ومع ذلك, أنا مندهش أحد جلبت إمكانية مترجم أن يدركوا أن متغير temp لا لزوم لها ، و تعيين مباشرة إلى m_PropName.إذا كان هذا حالة, ثم قراءة الموضوع أن ربما قراءة كائن لم انتهى يجري بناؤها.لا مترجم منع مثل هذه الحالة ؟

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

ومع ذلك ، فإن اللغة/وقت التشغيل لا يضمن أن المواضيع الأخرى لا ترى جزئيا شيدت وجوه هذا يعتمد على ما منشئ لا.

تحديث:

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

النظام المعتاد من الأحداث:

  • المبرمج يكتشف تأكدت قفل
  • المبرمج يكتشف الذاكرة الحواجز

بين هذين الحدثين ، يطلقون الكثير من كسر البرامج.

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

موجز:خيوط رمز تبدو حوالي 1000 × أسهل في الكتابة مما هو عليه.

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

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

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

بالمناسبة, قد لا تقلل من كود بكثير ، ولكن أنا معجب null-تتجمع المشغل.الجسم الخاص بك جالبة يمكن أن يصبح هذا بدلا من ذلك:

m_PropName = m_PropName ?? new ...();
return m_PropName;


فإنه يتخلص من الزائد "if (m_PropName == null) ..." و في رأيي يجعل أكثر إيجازا للقراءة.

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

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

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

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

هناك ثلاثة أنماط عامة واحدة قد تستخدم من أجل كسول التهيئة حسب الظروف:

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

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

هذا هو بالتأكيد مشكلة.

النظر في هذا السيناريو:موضوع "أ" بالوصول إلى مكان الإقامة ، وجمع يتم تهيئة.قبل أن يعين المحلية سبيل المثال إلى الميدان "m_PropName" ، موضوع "ب" الوصول إلى الملكية ، فيما عدا ذلك يحصل لإكمال.موضوع "B" لديها الآن الإشارة إلى تلك الحالة التي يتم تخزينها حاليا في "m_PropName"...حتى موضوع "A" لا يزال في نقطة "m_PropName" هو الكتابة المحلية سبيل المثال في هذا الموضوع.

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

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