كتاب "المنافسة في C #. برمجة غير متزامنة ومتوازية ومتعددة مؤشرات الترابط. ثاني int. إد. "

صورةمرحبا ، هابروجيتلي! إذا كنت خائفًا من البرمجة التنافسية متعددة الخيوط ، فهذا الكتاب مكتوب من أجلك. يحتوي Stephen Cleary على 85 وصفة للعمل مع .NET و C # 8.0 للمعالجة المتوازية والبرمجة غير المتزامنة. أصبحت المنافسة بالفعل الطريقة المقبولة لتطوير تطبيقات قابلة للتطوير بدرجة عالية ، لكن البرمجة المتزامنة تظل مهمة شاقة. ستساعد الأمثلة والتعليقات التفصيلية على الشفرة على فهم كيفية زيادة الأدوات الحديثة لمستوى التجريد وتبسيط البرمجة التنافسية. سوف تتعلم كيفية استخدام المتزامن والانتظار لعمليات غير متزامنة ، وتوسيع إمكانات التعليمات البرمجية من خلال استخدام خيوط غير متزامنة ، واستكشاف إمكانات البرمجة المتوازية مع مكتبة TPL Dataflow ،إنشاء خطوط أنابيب دفق البيانات باستخدام مكتبة TPL Dataflow ، واستخدام النظام المستند إلى LINQ. الوظائف التفاعلية ، واستخدام المجموعات الآمنة والخيطية غير القابلة للتغيير ، وإجراء اختبار الوحدة للشفرة التنافسية ، والتحكم في مجموعة مؤشرات الترابط ، وتنفيذ الإلغاء التعاوني الصحيح ، وتحليل البرامج النصية لدمج الطرق التنافسية ، استخدم جميع ميزات البرمجة الموجهة للكائنات المتوافقة بشكل غير متزامن ، والتعرف على المحولات وإنشائها للشفرة التي تستخدم الأنماط القديمة للبرمجة غير المتزامنة.تحليل البرامج النصية للجمع بين الأساليب التنافسية ، واستخدام جميع ميزات البرمجة الموجهة للكائنات المتوافقة بشكل غير متزامن ، والتعرف على المحولات وإنشائها للشفرة التي تستخدم الأنماط القديمة للبرمجة غير المتزامنة.تحليل البرامج النصية للجمع بين الأساليب التنافسية ، واستخدام جميع ميزات البرمجة الموجهة للكائنات المتوافقة بشكل غير متزامن ، والتعرف على المحولات وإنشائها للشفرة التي تستخدم الأنماط القديمة للبرمجة غير المتزامنة.

أساسيات البرمجة الموازية


4.1. معالجة البيانات المتوازية


مهمة


هناك مجموعة من البيانات. يجب إجراء نفس العملية مع كل عنصر بيانات. هذه العملية محدودة حسابياً وقد تستغرق بعض الوقت.

القرار


يحتوي النوع المتوازي على أسلوب ForEach ، المصمم خصيصًا لهذه المهمة. يحصل المثال التالي على مجموعة من المصفوفات ويدير هذه المصفوفات:

void RotateMatrices(IEnumerable<Matrix> matrices, float degrees)
{
   Parallel.ForEach(matrices, matrix => matrix.Rotate(degrees));
}

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

void InvertMatrices(IEnumerable<Matrix> matrices)
{
   Parallel.ForEach(matrices, (matrix, state) =>
   {
      if (!matrix.IsInvertible)
        state.Stop();
      else
        matrix.Invert();
   });
}

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

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

void RotateMatrices(IEnumerable<Matrix> matrices, float degrees,
      CancellationToken token)
{
   Parallel.ForEach(matrices,
         new ParallelOptions { CancellationToken = token },
         matrix => matrix.Rotate(degrees));
}

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

// :     .
//      
//    .
int InvertMatrices(IEnumerable<Matrix> matrices)
{
  object mutex = new object();
  int nonInvertibleCount = 0;
  Parallel.ForEach(matrices, matrix =>
  {
     if (matrix.IsInvertible)
    {
       matrix.Invert();
    }
    else
    {
       lock (mutex)
      {
         ++nonInvertibleCount;
      }
    }
  });
  return nonInvertibleCount;
}

تفسير


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

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

معلومة اضافية


تناقش الوصفة 4.2 التجميع الموازي لسلسلة من القيم ، بما في ذلك جمع وحساب المتوسطات.

الوصفة 4.5 تغطي أساسيات PLINQ.

يتناول الفصل 10 الإلغاء.

4.2. التجميع الموازي


مهمة


يلزم تجميع النتائج في نهاية العملية المتوازية (أمثلة على التجميع هي تجميع القيم أو حساب المتوسط).

القرار


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

// :     .
//      
//    .
int ParallelSum(IEnumerable<int> values)
{
  object mutex = new object();
  int result = 0;
  Parallel.ForEach(source: values,
        localInit: () => 0,
        body: (item, state, localValue) => localValue + item,
        localFinally: localValue =>
       {
          lock (mutex)
             result += localValue;
       });
  return result;
}

يوفر Parallel LINQ دعم تجميع أكثر شمولاً من الفئة الموازية:

int ParallelSum(IEnumerable<int> values)
{
   return values.AsParallel().Sum();
}

حسنًا ، كانت هذه خدعة رخيصة لأن PLINQ لديها دعم مدمج للعديد من المشغلين المشتركين (مثل Sum). يوفر PLINQ أيضًا دعمًا عامًا للتجميع مع عامل تشغيل Aggregate:

int ParallelSum(IEnumerable<int> values)
{
  return values.AsParallel().Aggregate(
        seed: 0,
        func: (sum, item) => sum + item
  );
}

تفسير


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

معلومة اضافية


توضح الوصفة 4.5 أساسيات PLINQ.

4.3. دعوة موازية


مهمة


هناك مجموعة من الأساليب التي يجب أن تسمى بالتوازي. هذه الأساليب (في الغالب) مستقلة عن بعضها البعض.

القرار


تحتوي فئة Parallel على طريقة Invoke بسيطة مصممة لمثل هذه السيناريوهات. في المثال التالي ، يتم تقسيم الصفيف إلى قسمين ، وتتم معالجة النصفين بشكل مستقل:

void ProcessArray(double[] array)
{
   Parallel.Invoke(
         () => ProcessPartialArray(array, 0, array.Length / 2),
         () => ProcessPartialArray(array, array.Length / 2, array.Length)
   );
}

void ProcessPartialArray(double[] array, int begin, int end)
{
   // ,   ...
}

يمكنك أيضًا تمرير مجموعة من المندوبين إلى أسلوب Parallel.Invoke إذا كان عدد المكالمات غير معروف قبل التنفيذ:

void DoAction20Times(Action action)
{
   Action[] actions = Enumerable.Repeat(action, 20).ToArray();
   Parallel.Invoke(actions);
}

Parallel.Invoke يدعم الإلغاء ، مثل الطرق الأخرى لفئة Parallel:

void DoAction20Times(Action action, CancellationToken token)
{
   Action[] actions = Enumerable.Repeat(action, 20).ToArray();
   Parallel.Invoke(new ParallelOptions { CancellationToken = token },
        actions);
}

تفسير


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

معلومة اضافية


تناقش الوصفة 4.1 طريقة Parallel.ForEach ، التي تقوم بإجراء لكل عنصر بيانات.

وصفة 4.5 تتعامل مع المتوازي LINQ.

نبذة عن الكاتب


انتقل ستيفن كليري ، المطور ذو الخبرة ، من ARM إلى Azure. ساهم في مكتبة Boost C ++ مفتوحة المصدر وأصدر العديد من المكتبات والمرافق الخاصة.

»يمكن العثور على مزيد من المعلومات حول الكتاب على موقع الناشر على الويب
» المحتويات
» مقتطفات

لـ Khabrozhiteley خصم 25 ٪ على القسيمة - Cleary

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

All Articles