PowerShell Multithreading: دراسة متعمقة

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

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

في هذا المقال، ستتعلم كيفية فهم واستخدام تقنيات التعدد الخيطي في PowerShell المختلفة لمعالجة تيارات البيانات المتعددة في نفس الوقت ولكن من خلال واجهة المستخدم نفسها.

فهم التعدد الخيطي في PowerShell

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

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

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

بدون التعددية في الخيوط، ستقوم بتشغيل أمر New-ADUser 1000 مرة لإنشاء جميع المستخدمين. قد يستغرق إنشاء مستخدم جديد ثلاث ثوانٍ. لإنشاء جميع المستخدمين الـ 1000، ستستغرق العملية قرابة ساعة واحدة. بدلاً من استخدام خيط واحد لـ 1000 أمر، يمكنك استخدام 100 خيط يعمل كل منها عشرة أوامر. الآن، بدلاً من أن تستغرق حوالي 50 دقيقة، ستستغرق العملية أقل من دقيقة واحدة!

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

متطلبات ما قبل التعددية في PowerShell

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

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

الأولوية الأولى: إصلاح الكود الخاص بك!

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

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

تحديد أسباب التباطؤ

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

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

المثال أدناه يقرأ كل العمليات الجارية ثم يقوم بتصفية عملية واحدة (svchost)، ثم يختار خاصية وحدة المعالجة المركزية ويتأكد من أن القيمة ليست فارغة.

PS51> Get-Process | Where-Object {$_.ProcessName -eq 'svchost'} | 
	Select-Object CPU | Where-Object {$_.CPU -ne $null}

قارن الشيفرة أعلاه بالمثال أدناه. فيما يلي مثال آخر على شيفرة لها نفس النتيجة ولكنها مرتبة بشكل مختلف. لاحظ أن الشيفرة أدناه أبسط وتحرك جميع العمليات الممكنة إلى اليسار من الرمز |. هذا يمنع Get-Process من إرجاع العمليات التي لا تهمك.

PS51> Get-Process -Name 'svchost' | Where-Object {$_.CPU -ne $null} | 
	Select-Object CPU

فيما يلي فرق الوقت لتشغيل السطرين السابقين. في حين أن الفرق البالغ 117 مللي ثانية قد لا يلاحظ إذا تم تشغيل هذا الشيفرة مرة واحدة فقط، إلا أنه سيبدأ في التراكم إذا تم تشغيلها آلاف المرات.

time difference for running the two lines from above

استخدام شيفرة آمنة للمواضيع

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

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

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

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

التنفيذ المتوازي باستخدام PSJobs

أحد أسهل الطرق لتعدد خيوط البرنامج النصي هو باستخدام PSJobs. تحتوي PSJobs على أوامر مضمنة في وحدة Microsoft.PowerShell.Core. تم تضمين وحدة Microsoft.PowerShell.Core في جميع إصدارات PowerShell منذ الإصدار 3. تسمح لك الأوامر في هذه الوحدة بتشغيل الكود في الخلفية مع الاستمرار في تشغيل كود مختلف في الواجهة الأمامية. يمكنك رؤية جميع الأوامر المتاحة أدناه.

PS51> Get-Command *-Job
Get-Command *-Job output

تتبع الوظائف الخاصة بك

تكون جميع PSJobs في إحدى الحالات الأحد عشر. هذه الحالات هي كيف يدير PowerShell الوظائف.

فيما يلي قائمة بأكثر الحالات الشائعة التي يمكن أن تكون فيها الوظيفة.

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

للحصول على حالة المهمة التي تم بدء تنفيذها ، يمكنك استخدام الأمر Get-Job. يحصل هذا الأمر على جميع سمات المهام الخاصة بك.

فيما يلي الناتج لمهمة حيث يمكنك رؤية أن الحالة هي مكتملة. في المثال أدناه يتم تنفيذ الكود Start-Sleep 5 داخل مهمة باستخدام الأمر Start-Job. ثم يتم إرجاع حالة تلك المهمة باستخدام الأمر Get-Job.

PS51> Start-Job -Scriptblock {Start-Sleep 5}
PS51> Get-Job
Get-Job output

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

فيما يلي مثال لبعض الحالات الأخرى المستخدمة لوصف المهام. يمكن رؤية من عمود Command أن محاولة النوم لمدة abc ثانية أدت إلى فشل المهمة.

All jobs

إنشاء مهام جديدة

كما شاهدت أعلاه ، يتيح لك الأمر Start-Job إنشاء مهمة جديدة تبدأ في تنفيذ الكود في المهمة. عند إنشاء مهمة ، تقدم سكريبتبلوك يستخدم في المهمة. ثم يقوم PSJob بإنشاء مهمة برقم تعريف فريد ويبدأ تشغيل المهمة.

الفائدة الرئيسية هنا هي أن الأمر “Start-Job” يستغرق وقتًا أقل من تشغيل كتلة البرنامج النصي التي نستخدمها. يمكنك أن ترى في الصورة أدناه أنه بدلاً من أن يستغرق الأمر خمس ثوانٍ لاستكماله، استغرق فقط 0.15 ثانية لبدء الوظيفة.

How long it takes to create a job

السبب في أنه تمكن من تشغيل نفس الكود في جزء صغير من الوقت هو أنه يعمل في الخلفية كـ PSJob. استغرقت 0.15 ثانية لإعداد وبدء تشغيل الكود في الخلفية بدلاً من تشغيله في الواجهة الأمامية والفعلية للنوم لمدة خمس ثوانٍ.

استرجاع نتائج الوظيفة

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

مثال على ذلك هو تشغيل الكود أدناه. سيقوم هذا بإنشاء وبدء وظيفة ستكتب “Hello World” في الناتج. ثم يسترجع الناتج من الوظيفة ويخرجه إلى واجهة الأوامر.

$Job = Start-Job -ScriptBlock {Write-Output 'Hello World'}
Receive-Job $Job
Receive-Job output

إنشاء وظائف مجدولة

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

مؤثرات المهمة

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

بالإضافة إلى وجود مؤثر ، ستحتاج لكتلة سكريبت مثلما يتم استخدامها في PSJob العادي. بمجرد الحصول على المؤثر وكتلة السكريبت ، يجب استخدام الأمر Register-ScheduledJob لإنشاء الوظيفة كما هو موضح في الجزء التالي. يتم استخدام هذا الأمر لتحديد سمات الوظيفة المجدولة مثل كتلة السكريبت التي ستتم تشغيلها والمؤثرات التي تم إنشاؤها باستخدام الأمر New-JobTrigger.

عرض توضيحي

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

للقيام بذلك ، يجب أن تقوم أولاً بتعريف مؤشر باستخدام New-JobTrigger وتعريف المهمة المجدولة كما هو موضح أدناه. ستقوم هذه المهمة المجدولة بكتابة سطر في ملف سجل كل مرة يتم فيها تسجيل الدخول.

$Trigger = New-JobTrigger -AtLogon
$Script = {"User $env:USERNAME logged in at $(Get-Date -Format 'y-M-d H:mm:ss')" | Out-File -FilePath C:\Temp\Login.log -Append}

Register-ScheduledJob -Name Log_Login -ScriptBlock $Script -Trigger $Trigger

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

Registering a scheduled job

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

Example login.log text file

استغلال معلمة AsJob

طريقة أخرى لاستخدام المهام هي باستخدام معلمة AsJob المدمجة في العديد من أوامر PowerShell. نظرًا لوجود العديد من الأوامر المختلفة ، يمكنك العثور على جميعها باستخدام Get-Command كما هو موضح أدناه.

PS51> Get-Command -ParameterName AsJob

واحدة من الأوامر الأكثر شيوعًا هي Invoke-Command. عادةً ما يبدأ تشغيل هذا الأمر تنفيذ أمر على الفور. في حين أن بعض الأوامر ستعود على الفور ، مما يتيح لك المتابعة فيما كنت تفعله ، فإن البعض الآخر سينتظر حتى انتهاء الأمر.

استخدام معلمة AsJob يفعل بالضبط ما يوحي به ويشغل الأمر المنفذ كمهمة بدلاً من تشغيله بشكل متزامن في وحدة التحكم.

على الرغم من أن معظم الوقت يمكن استخدام AsJob مع الجهاز المحلي ، إلا أن Invoke-Command ليس لديه خيار أصلي للتشغيل على الجهاز المحلي. هناك طريقة للتجاوز هي باستخدام Localhost كقيمة لمعلمة ComputerName. فيما يلي مثال لهذا الحل المؤقت.

PS51> Invoke-Command -ScriptBlock {Start-Sleep 5} -ComputerName localhost

لإظهار المعلمة AsJob في العمل، يستخدم المثال أدناه Invoke-Command للانتظار لمدة خمس ثوانٍ ثم يكرر نفس الأمر باستخدام AsJob لإظهار الفرق في أوقات التنفيذ.

PS51> Measure-Command {Invoke-Command -ScriptBlock {Start-Sleep 5}}
PS51> Measure-Command {Invoke-Command -ScriptBlock {Start-Sleep 5} -AsJob -ComputerName localhost}
Comparing speed of jobs

Runspaces: تشبه الأعمال ولكنها أسرع!

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

Runspaces هي المنطقة المحيطة التي تعمل فيها الخيوط التي تعمل باستخدام PowerShell. بينما يتم تقييد runspace المستخدم مع وحدة التحكم PowerShell إلى خيط واحد، يمكنك استخدام runspaces إضافية للسماح باستخدام خيوط إضافية.

Runspace مقابل PSJobs

بينما يتشابه runspace و PSJob في العديد من الجوانب، هناك اختلافات كبيرة في الأداء. أكبر اختلاف بين runspaces و PSjobs هو الوقت الذي يستغرقه إعداد وتفكيك كل واحد.

في المثال من القسم السابق، استغرق إنشاء PSjob حوالي 150 مللي ثانية للتشغيل. هذا هو أفضل حالة حيث لم يتضمن سكريبت العمل الكثير من الشفرات ولم يتم تمرير أي متغيرات إضافية إلى العمل.

على عكس إنشاء PSJob، يتم إنشاء runspace مسبقًا. يتم التعامل مع جزء كبير من الوقت الذي يستغرقه تشغيل runspace قبل إضافة أي شفرة.

فيما يلي مثال لتشغيل نفس الأمر الذي استخدمناه في PSJob في runspace بدلاً من ذلك.

Measuring speed of job creation

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

$Runspace = [runspacefactory]::CreateRunspace()
$PowerShell = [powershell]::Create()
$PowerShell.Runspace = $Runspace
$Runspace.Open()
$PowerShell.AddScript({Start-Sleep 5})
$PowerShell.BeginInvoke()
Measuring speed of creating a runspace

تشغيل Runspaces: دليل تفصيلي

قد يكون استخدام runspaces مهمة مربكة في البداية لأنه لا يوجد بعد أمر PowerShell يرشدك. ستضطر إلى التعامل مع فئات .NET مباشرة. في هذا القسم ، دعنا نحلل ما يلزم لإنشاء runspace في PowerShell.

في هذا الدليل التفصيلي ، ستقوم بإنشاء runspace منفصل من نافذة تحكم PowerShell الخاصة بك ونسخة منفصلة من PowerShell. ثم ستسند runspace الجديد إلى نسخة جديدة من PowerShell وتضيف الكود إلى تلك النسخة.

إنشاء Runspace

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

 $Runspace = [runspacefactory]::CreateRunspace()

الآن بعد إنشاء runspace ، قم بتعيينه إلى نسخة من PowerShell لتشغيل الأوامر في PowerShell. لهذا ، ستستخدم فئة “powershell” ومشابهة لـ runspace ، ستحتاج أيضًا إلى تخزين هذا في متغير مثل ما هو موضح أدناه.

 $PowerShell = [powershell]::Create()

بعد ذلك ، قم بإضافة runspace إلى نسخة PowerShell الخاصة بك ، افتح runspace لتتمكن من تشغيل الأوامر وأضف scriptblock الخاص بك. يتم عرض ذلك أدناه مع scriptblock للنوم لمدة خمس ثوانٍ.

 $PowerShell.Runspace = $Runspace
 $Runspace.Open()
 $PowerShell.AddScript({Start-Sleep 5})

تنفيذ Runspace

حتى الآن، لم يتم تشغيل كود النص بلوك بعد. كل ما تم حتى الآن هو تحديد كل شيء لـ الـrunspace. لبدء تشغيل كود النص بلوك، لديك خياران.

  • Invoke() – تشغيل الأمر Invoke() يقوم بتشغيل كود النص بلوك في الـrunspace، ولكنه ينتظر حتى يعود إلى واجهة الأوامر حتى يعود الـrunspace. هذا جيد للاختبار للتأكد من أن الكود يعمل بشكل صحيح قبل تشغيله بشكل كامل.
  • BeginInvoke() – استخدام الأمر BeginInvoke() هو ما سترغب في استخدامه لتحقيق زيادة في الأداء الفعلية. سيبدأ هذا تشغيل كود النص بلوك في الـrunspace ويعودك فورًا إلى واجهة الأوامر.

عند استخدام BeginInvoke()، قم بتخزين النتائج في متغير حيث ستحتاج إليه لرؤية حالة كود النص بلوك في الـrunspace كما هو موضح أدناه.

$Job = $PowerShell.BeginInvoke()

بمجرد الحصول على النتائج من BeginInvoke() وتخزينها في متغير، يمكنك التحقق من هذا المتغير لرؤية حالة المهمة كما هو موضح أدناه في خاصية IsCompleted.

the job is completed

سبب آخر ستحتاج إلى تخزين النتائج في متغير هو أنه على عكس الأمر Invoke()، فإن الأمر BeginInvoke() لن يعود تلقائيًا بالنتائج عند انتهاء الكود. للقيام بذلك، يجب عليك استخدام الأمر EndInvoke() بمجرد الانتهاء منه.

في هذا المثال، لن يكون هناك نتائج ولكن لإنهاء التشغيل، يمكنك استخدام الأمر أدناه.

$PowerShell.EndInvoke($Job)

عند الانتهاء من جميع المهام التي قمت بتخزينها في مساحة التشغيل، يجب عليك دائمًا إغلاق مساحة التشغيل. سيتيح ذلك لعملية تنظيف الذاكرة الغير المستخدمة في PowerShell. فيما يلي الأمر الذي يمكنك استخدامه للقيام بذلك.

$Runspace.Close()

استخدام مجموعات مساحة التشغيل

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

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

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

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

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

عرض سرعة مجموعة مساحة التشغيل

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

$Scriptblock = {
    param($Name)
    New-Item -Name $Name -ItemType File
}

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

بعد ذلك ، ستقوم الحلقة بالتكرار عشر مرات وفي كل مرة ستقوم بتعيين رقم التكرار إلى $_ . لذلك سيكون لديك 1 في التكرار الأول ، و 2 في التكرار الثاني وهكذا.

تقوم الحلقة بإنشاء كائن PowerShell ، وتعيين كتلة النص البرمجي ، والوسيطة للسكربت ، وتبدأ العملية.

في النهاية ، في نهاية الحلقة ، ستنتظر انتهاء جميع مهام الانتظار.

$Scriptblock = {
    param($Name)
    New-Item -Name $Name -ItemType File
}

$MaxThreads = 5
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads)
$RunspacePool.Open()
$Jobs = @()

1..10 | Foreach-Object {
	$PowerShell = [powershell]::Create()
	$PowerShell.RunspacePool = $RunspacePool
	$PowerShell.AddScript($ScriptBlock).AddArgument($_)
	$Jobs += $PowerShell.BeginInvoke()
}

while ($Jobs.IsCompleted -contains $false) {
	Start-Sleep 1
}

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

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

إنشاء مسابح الفضاء

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

$MaxThreads = 5
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads)
$PowerShell = [powershell]::Create()
$PowerShell.RunspacePool = $RunspacePool
$RunspacePool.Open()

مقارنة بين أماكن التشغيل ومجموعات أماكن التشغيل من حيث السرعة

لتوضيح الفرق بين مكان التشغيل ومجموعة أماكن التشغيل ، أنشئ مكان تشغيل وقم بتشغيل أمر Start-Sleep من الجزء السابق. هذه المرة ، ومع ذلك ، يجب تشغيلها 10 مرات. كما ترون في الشفرة أدناه ، يتم إنشاء مكان تشغيل سينام لمدة 5 ثوانٍ.

$Runspace = [runspacefactory]::CreateRunspace()
$PowerShell = [powershell]::Create()
$PowerShell.Runspace = $Runspace
$Runspace.Open()
$PowerShell.AddScript({Start-Sleep 5})

1..10 | Foreach-Object {
    $Job = $PowerShell.BeginInvoke()
    while ($Job.IsCompleted -eq $false) {Start-Sleep -Milliseconds 100}
}

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

من المثال أدناه ، يمكنك أن ترى أنه استغرق حوالي 51 ثانية لإكمال 10 مجموعات من فترة السكون لمدة 5 ثوانٍ.

Measuring performance of creating runspaces

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

$RunspacePool = [runspacefactory]::CreateRunspacePool(1, 5)
$RunspacePool.Open()
$Jobs = @()

1..10 | Foreach-Object {
    $PowerShell = [powershell]::Create()
    $PowerShell.RunspacePool = $RunspacePool
    $PowerShell.AddScript({Start-Sleep 5})
    $Jobs += $PowerShell.BeginInvoke()
}
while ($Jobs.IsCompleted -contains $false) {Start-Sleep -Milliseconds 100}

كما ترون أدناه ، يتم الانتهاء من هذا في أكثر من 10 ثوانٍ فقط ، مما يعد تحسنًا كبيرًا عن 51 ثانية لمكان التشغيل الواحد.

Comparing runspaces vs. runspace pools

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

Property Runspace Runspace Pool
Wait Delay Waiting for each job to finish before continuing to the next. Starting all of the jobs and then waiting until they have all finished.
Amount of Threads One Five
Runtime 50.8 Seconds 10.1 Seconds

الانتقال التدريجي إلى Runspaces مع PoshRSJob

A frequent occurrence when programming is that you will do what is more comfortable and accept the small loss in performance. This could be because it makes the code easier to write or easier to read, or it could just be your preference.

يحدث نفس الشيء في PowerShell حيث يستخدم بعض الأشخاص PSJobs بدلاً من runspaces بسبب سهولة الاستخدام. يمكن القيام ببعض الأشياء لتقسيم الفرق وتحسين الأداء دون جعلها أكثر صعوبة في الاستخدام.

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

لتثبيت الوحدة ، قم بتشغيل الأمر أدناه في جلسة PowerShell المسؤولية.

Install-Module PoshRSJob

بمجرد تثبيت الوحدة ، يمكنك أن ترى أن الأوامر هي نفس الأوامر PSJob مع بادئة RS. بدلاً من Start-Job ، هو Start-RSJob. بدلاً من Get-Job ، هو Get-RSJob.

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

run the same command in a PSJob and then again in an RSJob

أدناه بعض الشفرة التي يمكن استخدامها لمقارنة الفرق في السرعة بين PSJob و RSJob.

Measure-Command {Start-Job -ScriptBlock {Start-Sleep 5}}
Measure-Command {Start-RSJob -ScriptBlock {Start-Sleep 5}}

كما يمكن رؤية أدناه فهناك فرق سرعة كبير نظرًا لأن RSJobs لا يزالون يستخدمون runspaces أسفل الأغطية.

large speed difference since the RSJobs are still using runspaces below the covers

Foreach-Object -Parallel

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

حتى كتابة هذا النص، لا يزال PowerShell 7 في مرحلة المعاينة، لكنهم قاموا بإضافة معلمة “موازية” إلى أمر “Foreach-Object”. يستخدم هذا العملية runspaces لتوازي الكود ويستخدم السكربت المستخدم لـ “Foreach-Object” كسكربت للrunspace.

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

Measure-Command {1..10 | Foreach-Object {Start-Sleep 5}}
Measure-Command {1..10 | Foreach-Object -Parallel {Start-Sleep 5}}
Looping through and measuring commands

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

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

استخدام المتغيرات

واحدة من أكبر التحديات الواضحة في متعدد الخيوط هو أنه لا يمكنك مشاركة المتغيرات بدون تمريرها كوسائط. هناك استثناء واحد وهو “synchronized hashtable”، ولكن هذا موضوع لنقاش في وقت آخر.

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

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

بالنسبة لأوامر Start-Job و Start-RSJob من وحدة PoshRSJob ، يمكنك استخدام معلمة ArgumentList لتوفير قائمة من الكائنات التي ستتم تمريرها كمعاملات إلى كتلة النص الخاصة بالبرنامج النصي بالترتيب الذي تحدده. فيما يلي أمثلة للأوامر المستخدمة لـ PSJobs و RSJobs.

PSJob:

Start-Job -Scriptblock {param ($Text) Write-Output $Text} -ArgumentList "Hello world!"

RSJob:

Start-RSJob -Scriptblock {param ($Text) Write-Output $Text} -ArgumentList "Hello world!"

لا توفر عمليات التشغيل الأصلية نفس السهولة. بدلاً من ذلك ، يتعين عليك استخدام طريقة AddArgument() على كائن PowerShell. فيما يلي مثال على ما سيبدو عليه كل منهما.

Runspace:

$Runspace = [runspacefactory]::CreateRunspace()
$PowerShell = [powershell]::Create()
$PowerShell.Runspace = $Runspace
$Runspace.Open()
$PowerShell.AddScript({param ($Text) Write-Output $Text})
$PowerShell.AddArgument("Hello world!")
$PowerShell.BeginInvoke()

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

$MaxThreads = 5
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads)
$PowerShell = [powershell]::Create()
$PowerShell.RunspacePool = $RunspacePool
$RunspacePool.Open()
$PowerShell.AddScript({param ($Text) Write-Output $Text})
$PowerShell.AddArgument("Hello world!")
$PowerShell.BeginInvoke()

تسجيل

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

كمثال ، فيما يلي بعض الشفرة لمحاولة تسجيل 100 مرة في ملف واحد باستخدام 5 خيوط في مجموعة عمليات التشغيل.

$RunspacePool = [runspacefactory]::CreateRunspacePool(1, 5)
$RunspacePool.Open()
1..100 | Foreach-Object {
	$PowerShell = [powershell]::Create().AddScript({'Hello' | Out-File -Append -FilePath .\Test.txt})
	$PowerShell.RunspacePool = $RunspacePool
	$PowerShell.BeginInvoke()
}
$RunspacePool.Close()

من الناتج ، لن ترى أي أخطاء ، ولكن إذا نظرت إلى حجم ملف النص ، فيمكنك أن ترى أدناه أن لم تنته 100 وظيفة بشكل صحيح.

Get-Content -Path .\Test.txt).Count” class=”wp-image-3241″/>
(Get-Content -Path .\Test.txt).Count

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

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

ملخص

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

قراءة إضافية

Source:
https://adamtheautomator.com/powershell-multithreading/