تكنولوجيا

كيفية إجراء عمليات multithreading بأمان وكفاءة في .Web – CloudSavvy IT

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

معاملة المقارنة سيتم التحقق مما إذا كانت القيمتان متساويتين ، واستبدال القيمة إذا كانت متساوية.

استخدم مجموعات خيط آمنة

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

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

تبدو وكأنها حلقات متوازية

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

أفضل حل هو Procedure.Threading.Responsibilities.Parallel. مثل هذا البديل for مع foreach قم بتنفيذ حلقة جسم الحلقة على مؤشر ترابط منفصل. إنه سهل الاستخدام ، على الرغم من اختلاف التركيب قليلاً:

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

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

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

هناك الكثير من المعلمات ، وإليك الكثير من الأشياء لفك ضغطها:

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

تعدد خيوط الوحدة

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

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

مقالات ذات صلة

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

زر الذهاب إلى الأعلى