مقدمه
در درسهای گذشته دیدیم که مدیریت دستی نخها میتواند پیچیده و مستعد خطا باشد. برای سادهسازی
برنامهنویسی موازی و بهرهبرداری بهینه از پردازندههای چندهستهای مدرن، فریمورک .NET
یک کتابخانهی سطح بالا و قدرتمند به نام Task Parallel Library (TPL)
را معرفی کرده است. TPL که در فضای نام System.Threading.Tasks قرار دارد، به طور
هوشمند کارها را بین هستههای موجود تقسیم کرده و مدیریت نخها را از دید برنامهنویس پنهان میکند.
این کتابخانه به ما اجازه میدهد تا بر روی منطق موازیسازی تمرکز کنیم، نه بر روی
جزئیات مدیریت نخ. در این درس، با کلاس استاتیک Parallel به عنوان نقطهی ورود به دنیای
TPL آشنا میشویم.
کلاس استاتیک `Parallel`
کلاس System.Threading.Tasks.Parallel مجموعهای از متدهای استاتیک را
برای پیادهسازی الگوهای رایج موازیسازی فراهم میکند. این کلاس به طور خودکار دادهها یا تکرارها
را بین چندین نخ از استخر نخ (Thread Pool) تقسیم میکند تا به صورت موازی اجرا شوند.
حلقههای Parallel.For و Parallel.ForEach
دو مورد از پرکاربردترین متدهای کلاس Parallel، نسخههای موازی حلقههای for و foreach هستند.
این متدها برای سناریوهایی طراحی شدهاند که در آنها هر تکرار حلقه از تکرارهای دیگر مستقل است.
متد Parallel.For
این متد معادل موازی یک حلقهی for استاندارد است. این متد یک عمل (Action) را برای محدودهای از
اندیسها به صورت موازی اجرا میکند.
Program.cs
Console.WriteLine("Executing loop in parallel:");
Parallel.For(0, 10, i =>
{
Console.WriteLine($"Iteration {i} running on thread {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(100);
});
Console.WriteLine("Parallel.For has completed.");
اگر این کد را اجرا کنید، خواهید دید که ترتیب چاپ تکرارها تصادفی است و شناسهی نخها نیز متفاوت
است، که نشان میدهد تکرارها به صورت موازی بر روی نخهای مختلف در حال اجرا هستند.
متد Parallel.ForEach
این متد معادل موازی حلقهی foreach است و یک عملیات را بر روی تمام عناصر یک کالکشن
IEnumerable به صورت موازی اجرا میکند.
Program.cs
var filesToProcess = new List<string> { "file1.dat", "file2.dat", "file3.dat", "file4.dat" };
Parallel.ForEach(filesToProcess, file =>
{
Console.WriteLine($"Processing {file} on thread {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
});
اجرای موازی چندین عملیات با Parallel.Invoke
اگر چندین عملیات مستقل دارید و میخواهید آنها را به صورت موازی اجرا کنید، متد Parallel.Invoke
یک راهکار ساده و خوانا ارائه میدهد. این متد آرایهای از نمایندگان Action را به عنوان ورودی
میگیرد، همهی آنها را به صورت موازی شروع کرده و منتظر میماند تا تمام آنها به پایان برسند.
Program.cs
void Task1() { Console.WriteLine("Task 1 is running"); Thread.Sleep(1000); }
void Task2() { Console.WriteLine("Task 2 is running"); Thread.Sleep(1000); }
Parallel.Invoke(
() => Task1(),
() => Task2(),
() => Console.WriteLine("Task 3 is running")
);
Console.WriteLine("All parallel tasks have completed.");
چه زمانی از برنامهنویسی موازی استفاده کنیم؟
- کارهای محاسباتی سنگین (CPU-Bound): کلاس Parallel و TPL برای
کارهایی طراحی شدهاند که به شدت از CPU استفاده میکنند، مانند پردازش تصویر، محاسبات علمی، یا
الگوریتمهای پیچیده. موازیسازی این کارها میتواند زمان اجرا را به طور چشمگیری کاهش دهد.
- کارهای وابسته به I/O: برای کارهایی که منتظر یک عملیات ورودی/خروجی (I/O)
میمانند (مانند خواندن از فایل یا انتظار برای پاسخ شبکه)، استفاده از کلاس Parallel رویکرد
بهینهای نیست. این کار باعث اشغال شدن بیمورد نخهای Thread Pool میشود.
برای این سناریوها، باید از برنامهنویسی ناهمزمان با async و await استفاده کرد که در
درسهای آینده به آن میپردازیم.
- هزینهی سربار (Overhead): موازیسازی رایگان نیست. تقسیم کار و مدیریت نخها
خود هزینهی پردازشی دارد. برای کارهای بسیار کوچک و سریع، اجرای آنها به صورت موازی ممکن است
از اجرای ترتیبی (sequential) کندتر باشد.
- ایمنی نخ (Thread Safety): مهمترین نکته این است که کلاس Parallel مشکل
شرایط رقابتی را حل نمیکند. اگر تکرارهای موازی شما به دادههای مشترکی
دسترسی دارند، شما همچنان مسئول همگامسازی آن دسترسی با استفاده از lock یا دیگر مکانیزمهای
همگامسازی هستید.