مقدمه ای بر برنامه نویسی موازی در DotNet Framework 4.0
  در این مقاله معرفی کوتاهی نسبت به یکی از امکانات جدید ارائه شده در DotNet Framework 4.0 یعنی Task Parallel Library خواهیم داشت
   C#
   ۲۰۸۰۹
   این مقاله حاوی فایل ضمیمه نمی باشد
   مرتضی صحراگرد
   ۱۳۸۹/۸/۸
ارسال لینک صفحه برای دوستان ارسال لینک صفحه برای دوستان  اضافه کردن به علاقه مندیها اضافه کردن به علاقه مندیها   نسخه قابل چاپ نسخه قابل چاپ

 

مقدمه:

پردازنده هایی (CPU) که امروزه در سرور ها و رایانه های شخصی مورد استفاده قرار می گیرند، کاملا متفاوت با پردازنده های قدیمی می باشد.  پردازنده های جدید اغلب چند هسته ای بوده و قابلیت پردازش اطلاعات را به طور همزمان، موازی و توسط هسته های مختلف را دارند.

برنامه هایی که تاکنون توسط  DotNet Framework نوشته شده اند بیشتر آن ها فقط توسط یکی از هسته های پردازنده مورد پردازش قرار می گیرند. تاکنون جهت اجرای برنامه ها و وظایف (Tasks)به شکل موازی از ThreadPool و به طور کلی Multi-Thread Programming استفاده می شده است. ولی باید توجه داشت که با استفاده از این روش ها هنوز هم پردازش روی یکی از هسته ها انجام می شود و استفاده از ThreadPool به شکل کاملا درستی برنامه ها را به طور همزمان اجرا نمی کند.

تاکنون اگر توسعه گران قصد داشتند اجرای برنامه را با استفاده از هسته های مختلف پردازنده انجام دهند، باید خود را درگیر کدهای سطح پایین و بسیار پیچیده ای می نمودند. اما خوشبختانه با معرفی کتابخانه Task Parallel Library در DotNet Framework 4.0 این عمل به ساده ترین شکل ممکن امکان پذیر شده است.

کتابخانه ی Task Parallel Library یا به عبارت خلاصه تری TPL به طور کلی در سه حوزه ی زیر مورد استفاده قرار می گیرد

  1. Data Parallelism
  2. Parallel LINQ
  3. Task Parallelism

قبل از اینکه به ادامه مقاله بپردازیم، توجه داشته باشید که برای استفاده از امکانات کتابخانه TPL باید فضای نامی زیر را به برنامه اضافه نمایید.

using System.Threading.Tasks;

Data Parallelism:

یکی از نیاز های بسیار معمول برای برنامه نویسی موازی، پردازش سنگین اطلاعات می باشد. فرض کنید برنامه ی شما حاوی یک حلقه ی For یا ForEeach با تعداد تکرار های بسیار زیاد می باشد و در درون این حلقه نیز یک عمل زمانبر انجام می شود. این مسئله می تواند باعث هنگ نمودن برنامه و یا کاهش راندمان و کارایی برنامه گردد. تا زمانی که تمام تکرار های این حلقه به پایان نرسیده است این مشکل ادامه خواهد داشت. پس اگر بتوان این پردازش را با استفاده از هسته های مختلف پردازنده و به طور همزمان انجام داد، زمان پردازش به طور قابل توجهی کاهش می یابد و این امر باعث بهبود کارایی نرم افزار خواهد شد.

برای درک بهتر به مثال زیر توجه کنید.

Debug.WriteLine("Processor threads states:");
for (int i = 0; i < 10; i++)
{
    Debug.WriteLine(Thread.CurrentThread.ThreadState);
}

خروجی حلقه ی بالا به شکل زیر می باشد.


نکته ی مهمی که تصویر بالا بیان می کند، این است که پردازش حلقه فقط توسط ترید (Thread) جاری برنامه انجام می شود و در نتیجه تا زمانی که تمامی تکرار های حلقه انجام نشده باشد، این ترید مشغول می باشد.

اکنون این حلقه را با استفاده از کتابخانه TPL خواهیم نوشت.

Debug.WriteLine("Processor threads states:");

Parallel.For(0, 10, delegate(int i)
                    {
                        Debug.WriteLine(Thread.CurrentThread.ThreadState);

                    });

همانطور که ملاحظه می نمایید کلاس Parallelدارای متدی به نام For می باشد که امکان پردازش دستورات داخل حلقه به طور موازی بر روی هسته های مختلف پردازنده را فراهم می آورد. پارامتر اول حلقه، ایندکس شروع حلقه و پارامتر دوم ایندکس پایان حلقه و پارامتر سوم یک Action Delegate می باشد که ما در قطعه کد بالا آن را به شکل inline نوشته ایم.

در صورتی که تمایل نداشته باشیم که این Action Delegate را به شکل inline بنویسیم، می توانیم  از نام یک تابع ابه شکل زیر استفاده کنیم. (عملکرد آن کاملا مشابه می باشد)

Debug.WriteLine("Processor threads states:");
Parallel.For(0, 10, TestMethod);

void TestMethod(int i)
{
    Debug.WriteLine(Thread.CurrentThread.ThreadState);
}

اگر علاقه مند به عبارات لمبدا می باشید، این حلقه را به شکل بسیار ساده ای مانند قطعه کد زیر نیز می توان نوشت.

Debug.WriteLine("Processor threads states:");

Parallel.For(0, 10, i => Debug.WriteLine(Thread.CurrentThread.ThreadState));

اما به خروجی این حلقه توجه نمایید.


از شکل بالا به سادگی متوجه می شوید که پردازش این حلقه توسط ترید جاری برنامه (Running) و ترید های پس زمینه (Background) انجام شده است. در نتیجه ما بدون اینکه خود را درگیر مدیریت تریدهای مختلف کنیم، توانسته ایم برنامه را بر روی هسته های مختلف پردازنده به شکل موازی مورد پردازش قرار دهیم و این سهولت را مدیون کتابخانه ی TPL می باشیم.

نکته بسیار مهم:

توجه داشته باشید که اجرای این حلقه توسط ترید های مختلف، نکته ی دیگری را نیز به ما نمایان می کند و بیانگر آن است که در صورتی که داخل حلقه، دسترسی به منابع (Resources)خاصی پیدا می کنیم، حتما باید مدیریت دسترسی همزمان (Concurrent) را مد نظر داشته باشیم. ضمنا مسئله ی Thread-Safe نبودن کنترل های ویندوزی را هم در نظر داشته باشید.

کلاس Parallelدارای متدی به نام ForEachنیز می باشد که مانند حلقه ForEach معمولی عمل می کند و البته این پردازش نیز به صورت موازی انجام شده و مطالب گفته شده در مورد Parallel.For در مورد این متد نیز صادق می باشد.

به مثال زیر توجه فرمایید.

var customers = new List<Customer>();

Parallel.ForEach(customers, delegate(Customer c)
{
    Debug.WriteLine(c.FirstName);
}

 

Parallel LINQ:

Parallel LINQ که به شکل اختصار بدان PLINQ نیز گفته می شود، جهت اجرای کوئری های زبان LINQ بر روی هسته های مختلف پردازنده و به شکل موازی استفاده می شود.

قطعه کد زیر مثال ساده ای از یک کوئری با استفاده از LINQ می باشد.

var result = from m in customers
                where m.FirstName.StartsWith("mor")
                select m;

با استفاده از متد AsParallel می توانیم دستور کوئری بالا را بر روی هسته های مختلف پردازنده و به شکل موازی انجام دهیم.

var result = from m in customers.AsParallel()
                where m.FirstName.StartsWith("mor")
                select m;

توجه:

دقت داشته باشید که PLINQ راه حلی برای بالا بردن راندمان دستورات LINQ to SQL و LINQ to Entity نمی باشد.

 

Task Parallelism:

این ویژگی کتابخانه ی TPL امکان ایجاد وظایف (Tasks)، اجرا و مدیریت آن ها را فراهم می آورد. اجرای وظایف به شکل کاملا آسنکرون انجام می شود.

به مثال زیر دقت نمایید.

Task.Factory.StartNew(() =>
                            {
                                for (int i = 0; i < 10; i++)
                                {
                                    Debug.WriteLine(Thread.CurrentThread.ThreadState);
                                }
                            }

خروجی قطعه کد بالا به شکل زیر می باشد.


با استفاده از Task Parallelism می توان وظیفه ها را یکی پس از  کامل شده دیگری و یا به طور همزمان انجام داد.

تذکر بسیار مهم:

کتابخانه ی Task Parallel Library جهت استفاده از امکان اجرا نمودن کدها به شکل موازی به وجود آمده است ولی نباید تصور کنید که این کتابخانه باعث بالا بردن راندمان برنامه در تمامی حالات می شود. تنها زمانی از TPL استفاده کنید که عمل پردازش اطلاعات به شکل قابل توجهی زمانبر می باشد. در غیر این صورت استفاده از TPL تاثیری در راندمان برنامه نداشته و حتی کمی تاثیر منفی نیز می گذارد.

در ضمن می توان از کتابخانه TPL جهت اجرای کدها به شکل غیر همزمان و آسنکرون استفاده نمود. این عمل بدون درگیر شدن با تریدهای مختلف و مقوله ی پیچیده Multi-Thread Programming انجام می شود.