Blog chia sẽ kinh nghiệm lập trình và Technical VN. Nơi mà các bạn có thể học tập và tham khảo về những công nghệ mới nhất hiện nay về .NET

Hiển thị các bài đăng có nhãn multi thread. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn multi thread. Hiển thị tất cả bài đăng

Thứ Sáu, 20 tháng 3, 2015


Sau bài "Parallel Class Trong C# và Vấn Đề Xử lý Song Song" tôi nhận được khá nhiều câu hỏi về vấn đề này, ví dụ như chúng ta có nên dùng hoàn toàn Parallel.* thay cho cách truyền thống để được lợi về thời gian cũng như đỡ tốn công đồng bộ Data khi dùng Multi Thread. Hôm nay tôi sẽ nói về vấn đề này





I> Parallel và Multi Thread



Chúng ta có thể thấy rằng bản chất của Parallel cũng chỉ là Multi-Thread, song tại sao lại có Parallel khi trước đó đã có Multi Thread:



- Chúng ta được lợi nhiều hơn khi dùng Parallel thay cho Multi Thread bởi việc đồng bộ Data, quản lý Thread, các vấn đề về deadlock... đều đã có người làm việc đó thay mình(Microsoft chịu hoàn toàn trách nhiệm về sản phẩm của họ .. hi)

- Là multi Thread nhưng việc Executeing Thread đó trên hệ thống mà CPU là đa nhân thì làm sao cho hiệu quả cao nhất, Microsoft cũng giải quyết vấn đề này ở tần Framework rồi sau đó "đẻ" ra Parallel hay còn gọi là Multi-Core.

Vậy nên, Parallel xét về mặt tiện lợi và an toàn(phó thác sự tin tưởng) cũng như sẳn dùng cũng như multi-core thì hơn hẳn Multi Thread, xét về việc mà Dev có thể linh hoạt quản lý các Thread và data thì multi Thread đáng dùng hơn.



II> Parallel.* và cách duyệt/lặp truyền thống



Hình trên cho ta thấy cách mà Parallel.* làm việc, nếu mới biết về khái niệm Parallel một số bạn sẽ ưu ái sử dụng nó thay cho cách truyền thống(Parallel.For/ForEach thay cho for/foreach) nhưng chúng ta cần hiểu hơn về cách mà nó làm việc. Ví dụ:


- Dưới đây là một ví dụ mà Pararallel khai thác hết hiệu xuất CPU. Chúng ta có một công việc "my Job", parallel.* sẽ chia công việc của chúng ta ra n phần( tức for chạy từ 1 - n), những phần này không có sự ràng buộc với nhau về dữ liệu trong suốt time xử lý vì thế sẽ không có sự đợi chờ giữa các Thread và các Core. Chúng ta rất lợi về thời gian khi kết quả "my Result" không cần sự tuân thủ về thứ tự các Thread.




Thực tế: 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


=> Lưu ý rằng trong trường hợp trên nhưng time bị mất cho việc chia phần công việc lớn hơn nhiều so với tổng chi phí công việc phải làm thì Parallel không phát huy hiệu quả =>  rất chậm. Chia phần công việc được hiểu là việc hệ thống sẽ tiến hành phân tích và chia Thread chia Core, tính toán đủ thứ.



parallel tuanpham



- Dưới đây là ví dụ về việc dùng Parallel không mang lại hiệu quả. Chúng ta có một công việc, Parallel sẽ chia công việc thành n phần, chúng xử lý song song nhưng thực tế không phải vậy khi mà các Thread phải đợi nhau để đồng bộ dữ liệu hoặc truyền data cho nhau. Kết quả phụ thuộc vào thứ tự công việc của từng Thread.




Thực tế: 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à chiếc "Liềm" của 5 người đều bị "Cùn" nên cần mài cho "Bén", ngan trái thay chỉ có một cục đá mài nên phải Share nhau. Giả sử trường hợp tệ nhất là 5 cái "Liềm" cùng cần mài trong một thời điểm, thế thì phải xếp hàng đợi mài liềm rồi mới đi gặt(t2); cộng thêm thời gian chia phần đám ruộng(t1) cho 5 người trước khi gặt thì ta có t1 + t2 thời gian. Trong khi nếu trước đó chúng ta chọn phương án là thay nhau gặt lúa thì chúng ta chỉ mất có t2 thời gian. => for.foreach lợi hơn



parallel tuanphamdg




III> Kết luận


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




[code language="csharp"]
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();
}
}
}
[/code]

Kết quả




[code language="c"]
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
[/code]

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

Chủ Nhật, 28 tháng 12, 2014


Đa số các chip xử lý (CPU) hiện còn đang hoạt động mà chúng ta biết đến đều có trên hai nhân(lõi) và tính năng ảo hóa - điều này làm cho các máy tính có được tốc độ xử lý nhanh hơn và khả năng đa nhiệm tốt hơn. Chính vì vậy để tận dụng hết khả năng của CPU chúng ta cần xử lý đồng thời trên nhiều lõi CPU cho nhiều bài toán khác nhau để tiết kiệm thời gian và nâng cao hiệu xuất. Trong vấn đề trên và trong lập trình ứng dụng .NET nói riêng chúng ta có thuật ngữ Thread (luồng xử lý/tiểu trình), chúng ta sẽ tìm hiểu về nó trong bài viết này.




Bạn không nên nhầm lẫn giữa process (tiến trình) và thread (tiểu trình). Process có thể hiểu là một instance của chương trình máy tính được thực thi, dựa trên hệ điều hành, hoàn toàn độc lập với các tiến trình khác. Còn thread là một nhóm lệnh được tạo ra để thực thi một tác vụ trong một process, chúng chia sẻ chung dữ liệu với nhau để xử lý, điều này là cần thiết nhưng cũng là nguyên nhân dễ gây ra lỗi nếu bạn không xử lý đúng cách. Các bạn có thể tham khảo về Class Thread tại đây




[caption id="" align="aligncenter" width="735"]tuanphamdg Mô tả sự tiết kiệm về thời gian[/caption]

Trong nội dung này tôi không nói sâu về Thread trong C#, các bạn có thể tìm hiểm thêm tại đây, tôi sẽ xoay quanh về cách Thread hoạt động với các thành phần của WPF, xong tôi sẽ vẫn nhắc các bạn nhớ về Thread và Cách tạo một Thread như sau




[code language="csharp"]
public void CreateThread()
{
Thread thread = new Thread(new ThreadStart(MethodJob));
//MethodJob có thể là một delegate

//Run thread
thread.Start();

//Buộc hoãn thực thi đến khi được phép
thread.Suspend();

//Gọi thread tiếp tục chạy
thread.Resume();

//Buộc thread phải dừng
thread.Abort();

//Đợi thread xử lý xong
thread.Join();

}

private void MethodJob()
{
for (int i = 0; i < 100; i++)
{
//Do something
}
}
[/code]

Vấn đề Thread trong WPF - Truy cập an toàn một Control
Trong winform, một Thread phụ được tạo ra không thể tự mình truy cặp được các Control của Thread chính một cách an toàn được, chúng ta sẽ phải dùng delegate, trong WPF cũng thế; Nhưng trong WPF, mỗi Control đều chứa một đối tượng là Dispatcher - đối tượng này giúp đồng bộ các truy cập đến Control hiện tại và tránh sảy ra lỗi(tương tực việc lock(object)). Vì vậy, khi muốn thực hiện một truy cập an toàn đến một Control bất kỳ chúng ta cần gọi Dispatcher của control đó và phương thức xử lý Invoke(delegate), phương thức này chứa một delegate để làm việc với các thuộc tính của Control.


Ví dụ dưới đây phục vụ nhu cầu đếm lần lược từ 0->99 và hiện số đó lên TextBox




[code language="csharp"]
private void BtnRun_OnClick(object sender, RoutedEventArgs e)
{
//thread là một biến toàn cục kiểu Thread
if (thread != null)
{
thread.Abort(); //Dừng công viện hiện tại
}

//delegate được tạo theo định nghĩa Lambeda
thread = new Thread(new ThreadStart(() =>
{
//nội dung delegate
for (int i = 0; i < 100; i++)
{
Thread.Sleep(1000);

//An toàn truy cập với Dispatcher
txtData.Dispatcher.Invoke(() => txtData.Text = i.ToString());

//Vùng giới hạn an toàn rộng hơn
//this.Dispatcher.Invoke(() => txtData.Text = i.ToString());
}
}));

thread.IsBackground = true;
thread.Start();
}
[/code]

Ví dụ dưới đây phục vụ nhu cầu đếm lần lược từ x->99 và hiện số đó lên TextBox, với x là một số được truyền vào, tôi truyền vào số 10. Lưu ý tôi có hai delegate(Counter,Counters) chịu trách nhiệm hiển thị số đếm lên TextBox nhưng chúng có cách hiển thị khác nhau, các bạn hãy chú ý về cách truyền đối số cho Thread và hai delegate. Tôi gọi phương thức Invoke(delegate, object[]).




[code language="csharp"]
private void BtnRun2_OnClick(object sender, RoutedEventArgs e)
{
if (thread2 != null)
{
thread2.Abort();
}

thread2 = new Thread(new ParameterizedThreadStart(Run2));

thread2.IsBackground = true;
thread2.Start(10);
}

private delegate void Counter(int num);
private delegate void Counters(int num,int limit);

private void Run2(object numStart)
{
for (int i = (int)numStart; i < 100; i++)
{
//Cách truyền một tham số
this.Dispatcher.Invoke(new Counter(StartCount), i);

//Cách truyền nhiều tham số
this.Dispatcher.Invoke(new Counters(StartCounts), new object[] {i, 40});

Thread.Sleep(800);
}
}

private void StartCount(int num)
{
if (num < 20)
{
txtData2.Text = num.ToString();
}
else
{
txtData2.Text = "---";
}
}
private void StartCounts(int num,int limit)
{
if (num > 30 && num < limit)
{
txtData2.Text = num.ToString();
}
}
[/code]

Vấn đề Thread trong WPF - Show một Window từ Thread phụ
Nếu nhu cầu là một thread phụ được tạo ra để thực hiện một công việc nào đó và trong đó cần Show một Window thì bạn sẽ làm thế nào. Nếu bạn có thói quen dùng delegate như đoạn code bênh dưới chắc chắn rằng bạn sẽ gặp lỗi "The calling thread must be STA, because many UI components require this."




[code language="csharp"]
private void BtnRun4_OnClick(object sender, RoutedEventArgs e)
{
Thread thread5 = new Thread(new ThreadStart(ShowSubForm));
thread5.IsBackground = true;
thread5.Start();
}

private void ShowSubForm()
{
new MainWindow().Show();
}
[/code]

Vậy ta khắc phục lỗi trên như sau, cần tạo ra một Thread STA




[code language="csharp"]
private void BtnRun4_OnClick(object sender, RoutedEventArgs e)
{
Thread thread5 = new Thread(new ThreadStart(ShowSubForm));

thread5.SetApartmentState(ApartmentState.STA);
thread5.IsBackground = true;
thread5.Start();
}

private void ShowSubForm()
{
new MainWindow().Show();

//Nói rằng đây là một Window độc lập
System.Windows.Threading.Dispatcher.Run();
}
[/code]

Chúng ta còn cách khác đơn giản hơn với Dispathcher, bạn hảy thử




[code language="csharp"]
private void BtnRun4_OnClick(object sender, RoutedEventArgs e)
{
Thread thread5 = new Thread(new ThreadStart(ShowSubForm));

thread5.IsBackground = true;
thread5.Start();
}

private void ShowSubForm()
{
this.Dispatcher.Invoke(() =>
{
new MainWindow().Show();
});
}
[/code]

Vậy là chúng ta đã biết thêm về các vấn đề về Thread trong WPF, các bạn có thể tham khảo SourceCode
Chúc các bạn thành công!
Phạm Tuân

Thứ Hai, 22 tháng 12, 2014


Hôm nay chúng ta sẽ tìm hiểu về cơ chế xử lý song song trong C# với Parallel Class. 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.




Khi Nào Nên Dùng Lớp Parallel


Kiểm Tra Tốc Độ Của Foreach vs LinQ vs LinQasParallel


Hình minh họa dưới đây cung cấp một cái nhìn tổng quan cấp cao của kiến ​​trúc lập trình song song trong .NET Framework 4




[caption id="" align="aligncenter" width="672"]Phạm Tuân C# parallel Phạm Tuân[/caption]

Các bạn có thể tham khảo thêm chủ đề liên quan đến vấn đề này qua bản dưới.


















































TechnologyDescription
Task Parallel Library (TPL)Provides documentation for the System.Threading.Tasks.Parallel class, which includes parallel versions of For and ForEach loops, and also for theSystem.Threading.Tasks.Task class, which represents the preferred way to express asynchronous operations.
Parallel LINQ (PLINQ)A parallel implementation of LINQ to Objects that significantly improves performance in many scenarios.
Data Structures for Parallel ProgrammingProvides links to documentation for thread-safe collection classes, lightweight synchronization types, and types for lazy initialization.
Parallel Diagnostic ToolsProvides links to documentation for Visual Studio debugger windows for tasks and parallel stacks, and the Concurrency Visualizer, which consists of a set of views in the Visual Studio Application Lifecycle Management Profiler that you can use to debug and to tune the performance of parallel code.
Custom Partitioners for PLINQ and TPLDescribes how partitioners work and how to configure the default partitioners or create a new partitioner.
Task FactoriesDescribes the role of the System.Threading.Tasks.TaskFactory class.
Task SchedulersDescribes how schedulers work and how the default schedulers may be configured.
Lambda Expressions in PLINQ and TPLProvides a brief overview of lambda expressions in C# and Visual Basic, and shows how they are used in PLINQ and the Task Parallel Library.
For Further Reading (Parallel Programming)Provides links to additional documentation and sample resources for parallel programming in the .NET Framework.
Advanced Reading for the .NET FrameworkTop level node for advanced topics such as threading and parallel programming.

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




[code language="csharp"]
//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<MyObject> myEnumerable = ...

Parallel.ForEach(myEnumerable, obj =>
{
// ...
});
[/code]

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




[code language="csharp"]
IEnumerable<MyObject> 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<MyObject> myEnumerable = ...

myEnumerable.AsParallel().ForAll(obj => DoWork(obj));
[/code]

Lợi ích trước mắt chúng ta có thể nhận thấy đó là tốc độ và thời gian được cải thiện đáng kể, hãy xem xét ví dụ sau đây.




[code language="csharp"]
class Program
{
//Tính tổng
static int SumDefault(int[] array)
{
return array.Sum();
}

//Tính tổng với Parallel
static int SumAsParallel(int[] array)
{
return array.AsParallel().Sum();
}

static void Main()
{
// Tạo mảng.
int[] array = Enumerable.Range(0, short.MaxValue).ToArray();

// Test methods.
Console.WriteLine("Result for Parallel: " + SumAsParallel(array));
Console.WriteLine("Result for None-Parallel: " + SumDefault(array));

const int m = 10000;
var s1 = Stopwatch.StartNew();
for (int i = 0; i < m; i++)
{
SumDefault(array);
}
s1.Stop();

var s2 = Stopwatch.StartNew();
for (int i = 0; i < m; i++)
{
SumAsParallel(array);
}
s2.Stop();
Console.WriteLine("\nTime for None-paralel: "
+ ((double)(s1.Elapsed.TotalMilliseconds * 1000000) / m).ToString("0.00 ns"));

Console.WriteLine("Time for Paralel: "
+ ((double)(s2.Elapsed.TotalMilliseconds * 1000000) / m).ToString("0.00 ns"));

Console.Read();
}
}
[/code]

Các bạn có thể tìm hiểu thêm về parallel thông qua cuốn sách này - Parallel Programming with Microsoft .NET


Chúc các bạn thành công!


Phạm Tuân