Ultimate magazine theme for WordPress.

كيف تضاعف بأمان وفعالية في .NET – CloudSavvy IT

3

ads

يمكن استخدام Multithreading لتسريع أداء تطبيقك بشكل كبير ، ولكن لا يوجد تسريع مجاني – تتطلب إدارة الخيوط المتوازية برمجة دقيقة ، وبدون الاحتياطات المناسبة ، يمكنك مواجهة ظروف السباق ، والمآزق ، وحتى الأعطال.

ما الذي يجعل تعدد العمليات صعبًا؟

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

ومع ذلك ، فالأمر ليس بهذه البساطة مجرد “تشغيل تعدد مؤشرات الترابط”. فقط أشياء محددة (مثل الحلقات) يمكن أن تكون متعددة الخيوط بشكل صحيح ، وهناك الكثير من الاعتبارات التي يجب أخذها في الاعتبار عند القيام بذلك.

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

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

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

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

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

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

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

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

استخدم Interlocked للعمليات الذرية

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

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

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

هناك ايضا Increment و Decrement إلى عن على ++ و -- العمليات ، والتي ستوفر لك ضغطات مفاتيح قوية. التفاف حرفيا Add(ref count, 1) تحت غطاء المحرك ، لذلك ليس هناك تسريع محدد لاستخدامها.

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

ستتحقق CompareExchange من قيمتين من أجل المساواة ، وتستبدل القيمة إذا كانت متساوية.

استخدم مجموعات مؤشر الترابط الآمن

المجموعات الافتراضية بتنسيق System.Collections.Generic يمكن استخدامها مع تعدد مؤشرات الترابط ، لكنها ليست آمنة تمامًا مع مؤشرات الترابط. توفر Microsoft عمليات تنفيذ خيط آمنة لبعض المجموعات بتنسيق System.Collections.Concurrent.

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

انظر إلى الحلقات المتوازية

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

أفضل طريقة للتعامل مع هذا هو System.Threading.Tasks.Parallel. يوفر هذا الفصل بدائل لـ for و foreach الحلقات التي تنفذ أجسام الحلقة على خيوط منفصلة. إنه سهل الاستخدام ، على الرغم من أنه يتطلب صياغة مختلفة قليلاً:

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

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

دعنا نلقي نظرة على مثال. تأخذ الكود التالي قائمة كبيرة من الكائنات ، وتحتاج إلى إجراء تسلسل لكل عنصر على حدة إلى JSON ، وينتهي به الأمر بامتداد List<string> من كل الأشياء. تسلسل JSON هو عملية بطيئة جدًا ، لذا فإن تقسيم كل عنصر على خيوط متعددة يعد تسريعًا كبيرًا.

هناك مجموعة من الحجج ، والكثير لتفكيكه هنا:

  • تأخذ الحجة الأولى IEnumerable ، والتي تحدد البيانات التي يتم تكرارها. هذه حلقة ForEach ، لكن نفس المفهوم يعمل مع حلقات For الأساسية.
  • الإجراء الأول يهيئ متغير المجموع الفرعي المحلي. ستتم مشاركة هذا المتغير عبر كل تكرار للحلقة ، ولكن فقط داخل نفس الخيط. المواضيع الأخرى سيكون لها المجاميع الفرعية الخاصة بها. هنا ، نقوم بتهيئته إلى قائمة فارغة. إذا كنت تحسب إجماليًا رقميًا ، يمكنك ذلك return 0 هنا.
  • الإجراء الثاني هو جسم الحلقة الرئيسي. الوسيطة الأولى هي العنصر الحالي (أو الفهرس في حلقة For) ، والثانية هي كائن ParallelLoopState يمكنك استخدامه للاتصال .Break()، والأخير هو متغير المجموع الفرعي.
    • في هذه الحلقة ، يمكنك العمل على العنصر وتعديل المجموع الفرعي. ستحل القيمة التي ترجعها محل الإجمالي الفرعي للحلقة التالية. في هذه الحالة ، نقوم بتسلسل العنصر إلى سلسلة ، ثم نضيف السلسلة إلى المجموع الفرعي ، وهو عبارة عن قائمة.
  • أخيرًا ، يأخذ الإجراء الأخير “النتيجة” الإجمالي الفرعي بعد انتهاء جميع عمليات التنفيذ ، مما يسمح لك بقفل وتعديل مورد بناءً على الإجمالي النهائي. يتم تشغيل هذا الإجراء مرة واحدة ، في النهاية ، ولكنه لا يزال يعمل على سلسلة منفصلة ، لذلك ستحتاج إلى قفل أو استخدام أساليب Interlocked لتعديل الموارد. هنا نتصل AddRange() لإلحاق قائمة المجموع الفرعي بالقائمة النهائية.

الوحدة تعدد

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

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

ads

اترك رد

لن يتم نشر عنوان بريدك الإلكتروني.