Parallel سازي براي دات نت 2

۱۳۸۹/۰۷/۱۶ ۱۳:۴۲ Salar https://www.blogger.com/profile/08261083424775464146 منتشر شده در تاریخ : ۱۳۸۹/۰۷/۱۶ دسته بندی : ، ، ، ، 1

Parallel
همانطور که در دو پست قبلی مطالعه کردید در دات نت فریم ورک 4 قابلیت بسیار مفید parallel اضافه شده است. آن مجموعه کلاسها این امکان را فراهم می کنند که چندین کار در میان هسته های سیستم تقسیم شده و همزمان انجام شود. تنها عیب آن این است که فقط برای دات نت 4 در دسترس است.

برای حل این محدودیت می توان از روش های جایگزین استفاده کرد. گرچه این روشها باز هم به پای امکانات مهیا شده در دات نت 4 نمی رسد ولی باز هم بسیار راه گشا هستند. برای روش جایگزین می توان هم مستقیما از Thread ها استفاده کرد و هم از ThreadPool که کنترل thread ها را خودکار انجام می ده. البته این دو روش تفاوت فاحشی با هم دارند. تفاوت روش استفاده مستقیم از thread با threadpool عدم وابستگی به تعداد هسته cpu ها است، بدین معنا که در آن تقریبا همه آیتمها را به یکباره مورد پردازش قرار خواهند گرفت(البته به تعداد تعیین شده). اما threadpool همانند دات نت 4 عمل کرده و متدهای در حال اجرا و همزمان را به تعداد هسته cpu محدود می کند (با اندکی تفاوت). ابتدا متدی که مستقیما با thread ها کار می کند را معرفی می کنم و سپس روش threadpool را معرفی خواهم کرد.

با استفاده تابع زیر که تهیه کردم این امکان فراهم می آید که لیستی از آیتم ها را برای پردازش توسط یک متد یا رویه به طور همزمان پردازش نمایید. متد WorkAsParallel که در پایین ملاحظه می کنید دو ورودی می گیرد که ورودی اول لیستی جنریک از آیتم ها است و ورودی دوم متد پردازشگر مورد نظر که بر روی تک تک آیتم ها اجرا شده و عمل مورد تان را انجام می دهد.
public static void WorkAsParallel<T>(IList<T> itemsList, ParameterizedThreadStart itemDoWork)
{
// the maximum number of active threads
const int maxThreads = 10;
List<Thread> workers = new List<Thread>();

if (itemsList.Count > maxThreads)
{
    int currentItem = 0;
    for (int i = 0; i < maxThreads; i++)
    {
        T item = itemsList[i];
        try
        {
            Thread worker = new Thread(itemDoWork);
            worker.IsBackground = true;
            worker.Name = item.ToString();

            workers.Add(worker);

            // item index
            currentItem++;

            // start the thread
            worker.Start(item);
        }
        catch
        {
            // ignore any exception
        }
    }

    // A little break in current thread
    Thread.Sleep(5);

    do
    {
        // start waiting
        for (int i = workers.Count - 1; i >= 0; i--)
        {
            Thread worker = workers[i];

            if ((worker.ThreadState | ThreadState.Stopped) == ThreadState.Stopped)
            {
                workers.RemoveAt(i);

                // thread finished! run next irem if it is there
                if (currentItem <= itemsList.Count - 1)
                {
                    T item = itemsList[currentItem];
                    worker = new Thread(itemDoWork);
                    worker.IsBackground = true;
                    worker.Name = item.ToString();

                    workers.Add(worker);

                    // increase the item index
                    currentItem++;

                    // start the thread
                    worker.Start(item);
                }
            }
        }

        // a little break
        Thread.Sleep(5);

    } while (itemsList.Count > currentItem);

    // now all the list items are in progress

    // start waiting for them to be done
    foreach (Thread worker in workers)
    {
        // just wait for it
        // an infinity wait!
        worker.Join();
    }
}
else
{
    // all items are starting in threads
    foreach (T item in itemsList)
    {
        try
        {
            Thread worker = new Thread(itemDoWork);
            worker.IsBackground = true;
            worker.Name = item.ToString();

            workers.Add(worker);

            // start the thread
            worker.Start(item);
        }
        catch
        {
            // ignore any exception
        }
    }

    // A little break in current thread
    Thread.Sleep(5);

    // start waiting for them to be done
    foreach (Thread worker in workers)
    {
        // just wait for it
        // an infinity wait!
        worker.Join();
    }
}
}

به طور پیش فرض تعداد thread فعال 10 در نظر گرفته شده است (توسط ثابت maxThreads که قابل افزایش است) بدین معنا که در صورتی تعداد آیتمهای لیست شما بیش از 10 تا باشد ابتدا فقط 10 آیتم ابتدایی اجرا خواهند شد و تنها پس از پایان هر thread آیتم بعدی مورد پردازش قرار خواهد گرفت. این محدودیت به خاطر جلوگیری از وارد شدن فشار بیش از حد به سیستم در نظر گرفته شده و در صورت صلاحدید شما قابل افزایش است.

  • روش استفاده از WorkAsParallel

در مثال زیر یک لیست از DateTime با تاخیر زمانی 1 ثانیه چاپ می شود تا همزانی اجرا هم مشخص شود.
public void TestWorkAsParallel()
{
    List<DateTime> theList = new List<DateTime>();
    theList.Add(DateTime.Now.AddDays(1));
    theList.Add(DateTime.Now.AddDays(2));
    theList.Add(DateTime.Now.AddDays(3));

    ParallelWorks.WorkAsParallel(theList, x =>
    {
        DateTime item = (DateTime)x;
        Console.Write(item);
        Thread.Sleep(1000);
    });
}

و برای استفاده در VS 2005 که با کامپایلر CCS2 کار می کنه مثال مذبور رو به این تغییر بدید:
ParallelWorks.WorkAsParallel(theList, delegate(object x)
{
    DateTime item = (DateTime) x;
    Console.Write(item);
    Thread.Sleep(1000);
});

  • استفاده از ThreadPool

متد مورد نظر من در این پست از وبلاگ معرفی شده است. این متد لیستی از متد های قابل اجرا را دریافت کرده و به صورت همزمان اجرا می کند.
public class ParallelProcessor
{
    public delegate void Method();

    /// <summary>
    /// Executes a set of methods in parallel and returns the results
    /// from each in an array when all threads have completed.  The methods
    /// must take no parameters and have no return value.
    /// </summary>
    /// <param name="m"></param>
    /// <returns></returns>
    public static void ExecuteParallel(params Method[] methods)
    {
        // Initialize the reset events to keep track of completed threads
        ManualResetEvent[] resetEvents = new ManualResetEvent[methods.Length];

        // Launch each method in it's own thread
        for (int i = 0; i < methods.Length; i++)
        {
            resetEvents[i] = new ManualResetEvent(false);
            ThreadPool.QueueUserWorkItem(new WaitCallback((object index) =>
            {
                int methodIndex = (int)index;

                // Execute the method
                methods[methodIndex]();

                // Tell the calling thread that we're done
                resetEvents[methodIndex].Set();
            }), i);
        }

        // Wait for all threads to execute
        WaitHandle.WaitAll(resetEvents);
    }
}

روش استفاده آن هم همانطور که در آن پست ذکر شده آسان است.
ParallelProcessor.ExecuteParallel(() =>
{
    Console.WriteLine("The long task 1");
    Thread.Sleep(500);
}, () =>
{
    Console.WriteLine("The long task 2");
    Thread.Sleep(500);
},
() =>
{
    Console.WriteLine("The long task 3");
    Thread.Sleep(500);
});

و برای VS2005 از این روش فراخوانی استفاده کنید:
ParallelProcessor.Method[] methods = new ParallelProcessor.Method[3];
methods[0] = delegate{
    Console.WriteLine("The long task 1");
    Thread.Sleep(500);
};
methods[1] = delegate{
    Console.WriteLine("The long task 1");
    Thread.Sleep(500);
};
methods[2] = delegate{
    Console.WriteLine("The long task 1");
    Thread.Sleep(500);
};
ParallelProcessor.ExecuteParallel(methods);

  • مقایسه WorkAsParallel و ParallelProcessor در عمل

مسلما مقایسه این دو روش زیاد صحیح نخواهد بود. این به علت متفاوت بودن شیوه اجرای این دو روش است. به هرحال جهت نمایش این تفاوت مثال زیر را اجرا می کنیم:
static void Main(string[] args)
{
    Console.WriteLine("Parallels for .NET 2, salarblog.wordpress.com");

    // The list
    List<int> taskList = new List<int>() { 1, 2, 3, 4, 5, 6 };

    var pwatch = Stopwatch.StartNew();
    ParallelWorks.WorkAsParallel(taskList, x =>
    {
        Console.WriteLine("The long task {0} started at {1}", x, DateTime.Now.ToString("ss:fff"));
        Thread.Sleep(1000);
    });
    pwatch.Stop();
    Console.WriteLine("WorkAsParallel done!");

    var twatch = Stopwatch.StartNew();
    ParallelProcessor.ExecuteParallel(delegate
    {
        Console.WriteLine("Task 1 started at " + DateTime.Now.ToString("ss:fff"));
        Thread.Sleep(1000);
    }, delegate
    {
        Console.WriteLine("Task 2 started at " + DateTime.Now.ToString("ss:fff"));
        Thread.Sleep(1000);
    }, delegate
    {
        Console.WriteLine("Task 3 started at " + DateTime.Now.ToString("ss:fff"));
        Thread.Sleep(1000);
    }, delegate
    {
        Console.WriteLine("Task 4 started at " + DateTime.Now.ToString("ss:fff"));
        Thread.Sleep(1000);
    }, delegate
    {
        Console.WriteLine("Task 5 started at " + DateTime.Now.ToString("ss:fff"));
        Thread.Sleep(1000);
    }, delegate
    {
        Console.WriteLine("Task 6 started at " + DateTime.Now.ToString("ss:fff"));
        Thread.Sleep(1000);
    });
    twatch.Stop();

    Console.WriteLine();
    Console.WriteLine("WorkAsParallel finished in {0} seconds", pwatch.Elapsed.TotalSeconds);
    Console.WriteLine("ParallelProcessor finished in {0} seconds", twatch.Elapsed.TotalSeconds);

    // wait
    Console.ReadKey();
}

نتیجه اجرا چیزی مانند این خواهد بود (این نتیجه سیستم دو هسته من هست):
WorkAsParallel v.s. ParallelProcessor[/caption]به زمانها دقت کنید. تفاوت فاهش است، 3 ثانیه برای ParallelProcessor در مقابل 1 ثانیه برای WorkAsParallel. اما این دلیل بر بهتر بودن WorkAsParallel نیست. اگر به زمانهای شروع هر تسک نگاه کنید می بینید که تسک های متد WorkAsParallel همه تقریبا همزمان اجرا شده اند. (زمانها به صورت میلی ثانیه:ثانیه هستند). دلیل این امر را همانطور که گفتیم استفاده مستقیم از thread است.

در هر صورت این دو توابع در دسترس هستند و می توانید با توجه به نیاز خود هر کدام را که خواستید انتخاب کرده و مورد استفاده قرار دهید.

 

یک بازخورد برای “Parallel سازي براي دات نت 2”

  1. سلام دوست عزیز
    در صفحه ی زیر لینک شده اید:
    http://icnd.ir/pages/link

    در صورت تمایل به تبادل لینک پس از درج لینک ICND.ir
    در نظرات خبر دهید تا لینک شما در تمام صفحات قرار بگیرد.
    متشکرم

    پاسخحذف