02/10/2018, 00:57

[C#] Giới thiệu và hướng dẫn sử dụng Parallel trong lập trình .NET

Hôm nay mình xin giới thiệu và hướng dẫn các bạn sử dụng lớp Parallel trong C# . Vậy parallel trong C# là gì? và sử dụng nó như thế nào cho đúng. Parallel là gì? Parallel Class là cơ chế xử lý song song trong C#. Nhiều máy tính cá nhân và máy ...

Hôm nay mình xin giới thiệu và hướng dẫn các bạn sử dụng lớp Parallel trong C#. Vậy parallel trong C# là gì? và sử dụng nó như thế nào cho đúng.

Parallel là gì?

Parallel Class là cơ chế xử lý song song trong C#. Nhiều máy tính cá nhân và máy trạm có hai hoặc bốn lõi (CPU) cho phép nhiều luồng xử lý (Thread) được thực hiện đồng thời. Máy tính trong tương lai gần dự kiến ​​sẽ  còn có nhiều hơn số lỗi hiện tại. Để tận dụng lợi thế của phần cứng của ngày hôm nay và ngày mai, bạn có thể phân phối công việc trên nhiều luồng xử lý trên nhiều lõi. Visual Studio 2010 và .NET Framework 4 hỗ trợ tăng cường cho lập trình song song bằng cách cung cấp một runtime mới, các loại thư viện lớp mới, và các công cụ chẩn đoán mới. Những tính năng đơn giản hóa phát triển song song để bạn có thể viết hiệu quả, khả năng mở rộng và đa dạng tiến trình song song.

parallel trong c#

Trường hợp 1: Nên dùng Parallel

Một đám ruộng cần phải gặt song càng sớm càng tốt, chúng ta có 5 người( tức Parallel.For chạy từ 1 – 5) , mỗi người sẽ gặt 1/5 đám ruộng, ai song trước thì nghĩ( là Parallel.For/ForEach) . Y vậy, nếu việc gặt lúa song song cho ta lợi 3t về time và việc chia phần đám ruộng cho 5 người mất 1t thì chúng ta vẫn lợi 2t về thời gian hơn so với việc 5 người lần lược thay nhau gặt ( là for/foreach). => Parallel.* lợi hơn

Trường hợp 2: không dùng parallel

Một đám ruộng cần phải gặt song càng sớm càng tốt, chúng ta có 5 người( tức Parallel.For chạy từ 1 – 5) , vấn đề ở đây là 5 người nhưng chỉ có 1 chiếc “Liềm” nên phải share cho nhau. Vậy cuối cùng cũng chỉ có duy nhất một người gặt tại một thời điểm => chẳng khác gì thay nhau gặt cộng thêm thời gian chia phần đám ruộng cho 5 người trước khi gặt thì ta mất nhiều thời gian hơn(vi nếu bạn chia phần công việc trong code hay trước khi run ứng dụng thì lúc run-time CPU sẽ không cần thực thi việc phân chia core/thread) => for.foreach lợi hơn

Cách khai báo và sử dụng lớp Parallel trong C#.

//Cách dùng for bình thường ta vẫn hay dùng
int n = ...
for (int i = 0; i < n; i++)
{
   // ...
}
 
//Cách dùng với lớp parallel
int n = ...
Parallel.For(0, n, i =>
{
   // ...
});
 
//Hoặcvới một IEnumerable
 
IEnumerable myEnumerable = ...
 
Parallel.ForEach(myEnumerable, obj =>
{
   // ...
});

Vẫn có thể dùng với LINQ

IEnumerable source = ...
 
// LINQ
var query1 = from i in source select Normalize(i);
 
// PLINQ = parallel LINQ
var query2 = from i in source.AsParallel()
             select Normalize(i);
//Hoặc
IEnumerable myEnumerable = ...
 
myEnumerable.AsParallel().ForAll(obj => DoWork(obj));

Đây là bài test lý tưởng với Parallel:

namespace ParallelTests
{
    class Program
    {
        private static int Fibonacci(int x)
        {
            if (x <= 1) { return 1; } return Fibonacci(x - 1) + Fibonacci(x - 2); } private static void DummyWork() { var result = Fibonacci(10); // inspect the result so it is no optimised away. // We know that the exception is never thrown. The compiler does not. if (result > 300)
            {
                throw new Exception("failed to to it");
            }
        } 
 
        private const int TotalWorkItems = 2000000; 
 
        private static void SerialWork(int outerWorkItems)
        {
            int innerLoopLimit = TotalWorkItems / outerWorkItems;
            for (int index1 = 0; index1 < outerWorkItems; index1++)
            {
                InnerLoop(innerLoopLimit);
            }
        } 
 
        private static void InnerLoop(int innerLoopLimit)
        {
            for (int index2 = 0; index2 < innerLoopLimit; index2++) { DummyWork(); } } private static void ParallelWork(int outerWorkItems) { int innerLoopLimit = TotalWorkItems / outerWorkItems; var outerRange = Enumerable.Range(0, outerWorkItems); Parallel.ForEach(outerRange, index1 =>
            {
                InnerLoop(innerLoopLimit);
            });
        } 
 
        private static void TimeOperation(string desc, Action operation)
        {
            Stopwatch timer = new Stopwatch();
            timer.Start();
            operation();
            timer.Stop(); 
 
            string message = string.Format("{0} took {1:mm}:{1:ss}.{1:ff}", desc, timer.Elapsed);
            Console.WriteLine(message);
        } 
 
        static void Main(string[] args)
        {
            TimeOperation("serial work: 1", () => Program.SerialWork(1));
            TimeOperation("serial work: 2", () => Program.SerialWork(2));
            TimeOperation("serial work: 3", () => Program.SerialWork(3));
            TimeOperation("serial work: 4", () => Program.SerialWork(4));
            TimeOperation("serial work: 8", () => Program.SerialWork(8));
            TimeOperation("serial work: 16", () => Program.SerialWork(16));
            TimeOperation("serial work: 32", () => Program.SerialWork(32));
            TimeOperation("serial work: 1k", () => Program.SerialWork(1000));
            TimeOperation("serial work: 10k", () => Program.SerialWork(10000));
            TimeOperation("serial work: 100k", () => Program.SerialWork(100000)); 
 
            TimeOperation("parallel work: 1", () => Program.ParallelWork(1));
            TimeOperation("parallel work: 2", () => Program.ParallelWork(2));
            TimeOperation("parallel work: 3", () => Program.ParallelWork(3));
            TimeOperation("parallel work: 4", () => Program.ParallelWork(4));
            TimeOperation("parallel work: 8", () => Program.ParallelWork(8));
            TimeOperation("parallel work: 16", () => Program.ParallelWork(16));
            TimeOperation("parallel work: 32", () => Program.ParallelWork(32));
            TimeOperation("parallel work: 64", () => Program.ParallelWork(64));
            TimeOperation("parallel work: 1k", () => Program.ParallelWork(1000));
            TimeOperation("parallel work: 10k", () => Program.ParallelWork(10000));
            TimeOperation("parallel work: 100k", () => Program.ParallelWork(100000)); 
 
            Console.WriteLine("done");
            Console.ReadLine();
        }
    }
}

KẾT QUẢ:

serial work: 1 took 00:02.31
serial work: 2 took 00:02.27
serial work: 3 took 00:02.28
serial work: 4 took 00:02.28
serial work: 8 took 00:02.28
serial work: 16 took 00:02.27
serial work: 32 took 00:02.27
serial work: 1k took 00:02.27
serial work: 10k took 00:02.28
serial work: 100k took 00:02.28 
 
parallel work: 1 took 00:02.33
parallel work: 2 took 00:01.14
parallel work: 3 took 00:00.96
parallel work: 4 took 00:00.78
parallel work: 8 took 00:00.84
parallel work: 16 took 00:00.86
parallel work: 32 took 00:00.82
parallel work: 64 took 00:00.80
parallel work: 1k took 00:00.77
parallel work: 10k took 00:00.78
parallel work: 100k took 00:00.77

Parallel là một giải pháp tốt cho multi-Thread và multi-Core, nhưng chúng ta không nên lạm dụng. Hãy hiểu rõ bản chất vấn đề và hiện thực bạn đang đối mặt để chọn gải pháp tốt nhất. Phải chắc chắn rằng bài toán ban đang giải quyết là một bài toán Parallelizable thì mới dùng parallel. Cuối cùng, nên nhớ rằng “đừng lấy dao mổ trâu đi giết gà” và ngược lại. Trên đây là sự hiểu biết cá nhân, rất mong được góp ý.

Phạm Tuân

Tags: bất đồng bộ csharplập trìnhlap trinh
0