Tuy nhiên, đối với đa số các lập trình viên phổ thông thao tác lập trình cho cácphần mềm hoạt động tối ưu trên các CPU đa nhân vẫn còn đang là vấn đề khó khăn.Khó khăn vì lập trình song
Trang 1ĐẠI HỌC QUỐC GIA TP HỒ CHÍ MINH TRƯỜNG ĐẠI HỌC CÔNG NGHỆ THÔNG TIN
TP Hồ Chí Minh, tháng 7 năm 2013
Trang 2MỤC LỤC
1 Mở đầu .2
2 Các kỹ thuật lập trình song song .3
2.1 Lập trình tác vụ 3
2.1.1 Cơ bản về tác vụ 3
2.1.2 Thực hiện dừng tác vụ 4
2.1.3 Thực hiện chờ tác vụ 6
2.1.4 Bắt ngoại lệ khi chạy tác vụ 8
2.1.5 Các lỗi thường và cách xử lý 9
2.2 Chia sẻ dữ liệu 10
2.2.1 Đồng bộ hóa tác vụ 10
2.2.2 Các kỹ thuật đồng bộ hóa tác vụ 11
2.2.3 Cấu trúc dữ liệu dùng để chia sẻ dữ liệu 16
2.3 Điều hướng tác vụ 18
2.3.1 Sử dụng kỹ thuật Task Continuations 18
2.3.2 Tạo tác vụ con 19
2.4 Vòng lặp song song 21
3 Kết luận .22
TÀI LIỆU THAM KHẢO 23
Trang 31 Mở đầu
Lĩnh vực công nghệ thông tin đã được ứng dụng rộng rãi trong tất cả các lĩnh vựccủa cuộc sống Hiện nay, công nghệ thông tin đã được xem như là các nền tảng cốt lõitrong một số lĩnh vực, đặc biệt các lĩnh vực liên quan đến tính toán và mô phỏng Vớicác yêu cầu xử lý tính toán hiệu năng cao tăng nhanh về số lượng của như chất lượng
ví dụ như dự báo thời tiết, mô phỏng sinh học… các máy tính phải được nâng cấp liêntục về phần cứng để có được tính toán hiệu năng cao Xu hướng mở rộng các bán dẫn(transitors) trên các CPU truyền thống đã đạt nhiều thành công trong quá khứ nhưngđối với hiện tại thì việc mở rộng đã tiến tới gần những giới hạn nhất định Trong khiyêu cầu mở rộng về năng lực tính toán là không giới hạn nhưng việc gia tăng transitortrên đơn nhân CPU lại gặp giới hạn về kỹ thuật thì xu hướng đa nhân hóa các CPUtrên máy tính đang là xu hướng chính trong việc mở rộng năng lực tính toán cho cácmáy tính Hiện nay, đối với các máy tính phổ thông đã tiếp cận cơ bản với CPU có 8nhân riêng biệt để giải quyết nhanh chóng nhiều vấn đề riêng biệt khác nhau
Tuy nhiên, đối với đa số các lập trình viên phổ thông thao tác lập trình cho cácphần mềm hoạt động tối ưu trên các CPU đa nhân vẫn còn đang là vấn đề khó khăn.Khó khăn vì lập trình song song hay mở rộng ra là tính toán song song là nhữngphương pháp thao tác khó, khó sửa lỗi chương trình, khó phát triển phần mềm theomột số quy trình phần mềm hiện nay… Nhưng với những yêu cầu về tính toán hiệnnay thì xu hướng lập trình song song là xu hướng tất yếu đối với các lập trình viên.Trong phạm vi tiểu luận này, tác giả trình bày một số kỹ thuật cơ bản nhất trong lậptrình song song trên môi trường Net 4 với ngôn ngữ lập trình là C sharp Sở dĩ, lựachọn ngôn ngữ C sharp và môi trường Net vì:
- Tính phổ thông của C sharp và môi trường Net: hiện nay C sharp là một trong
ba ngôn ngữ lập trình phổ biến nhất thế giới với môi trường Net dễ dàng cài đặt
và phân phối phần mềm (tương tự như java runtime – jdk của ngôn ngữ Java)
- Tính hỗ trợ lập trình viên đầu cuối: C sharp và .Net được phát triển bởiMicrosoft nên đã có những hỗ trợ khá nhiều đối với những lập trình đầu cuối(những người viết trực tiếp ra sản phẩm phần mềm ứng dụng) Hỗ trợ này thểhiện qua việc triển khai lập trình nhanh chóng, dễ sửa lỗi, hỗ trợ các thư viện –
Trang 4API cũng như tính thống nhất – không phân tán do chỉ có Microsoft chịu tráchnhiệm.
Các kỹ thuật lập trình song song được tham khảo từ tài liệu tham khảo [1] và [2].Trong đó, [1] đảm nhận về các quan điểm lý thuyết và [2] là những kỹ thuật lập trìnhsong song
2 Các kỹ thuật lập trình song song
2.1 Lập trình tác vụ
2.1.1 Cơ bản về tác vụ
Tác vụ (task) là nền tảng cơ bản trong lập trình song song Trong đó, mỗi tác vụchứa các lệnh thực thi chương trình và mỗi tác vụ được cung cấp cho những nhânCPU đang trống Việc điều phối tác vụ cho nhân CPU xử lý được thực hiện bởi bộđịnh thời mặc định trong Net 4
using System;
using System.Threading.Tasks;
namespace Listing_04 {
class Listing_04 {
static void Main( string [] args) {
string [] messages = { "First task" , "Second task" ,
"Third task" , "Fourth task" };
foreach ( string msg in messages) {
Task myTask = new Task (obj => printMessage(( string )obj), msg); myTask.Start();
}
// wait for input before exiting
Console WriteLine( "Main method complete Press enter to finish." ); Console ReadLine();
}
static void printMessage( string message) {
Console WriteLine( "Message: {0}" , message);
Trang 5Kết quả đạt được là những “First Task”, “Third Task”, “Second Task” ngẫu nhiênkhông theo thứ tự Vì bộ định thời sẽ quyết định những luồng (thread) cho tác vụ vàmỗi luồng là khác nhau về thời gian xử lý nên kết quả đạt được mỗi lần chạy là khácnhau.
Tác vụ cũng có thể được xem là một hàm có giá trị trả về
Task < int > task1 = Task Factory.StartNew< int >(() => {
Console WriteLine( "Result 1: {0}" , task1.Result);
Ví d 2.2: giá tr tr v c a tác vụ 2.2: giá trị trả về của tác vụ ị trả về của tác vụ ả về của tác vụ ề của tác vụ ủa tác vụ ụ 2.2: giá trị trả về của tác vụ2.1.2 Thực hiện dừng tác vụ
Dừng tác vụ được thực hiện bởi những cơ bản sau:
- Tạo một thực thể mới của CancellationTokenSource để khai báo việc dừng tácvụ
- Tạo một token từ chối tác vụ CancellationToken Token này để xác định dừngnhững tác vụ cụ thể Token này được tạo ra thực thể của
CancellationTokenSource.
- Gán token cho tác vụ cần dừng Task task1 = new Task(new Action(myMethod), token);
- Sau khi khởi động tác vụ Muốn dừng tác vụ thì từ thực thể của
CancellationTokenSource sau đó là phương thức Cancel()
Từ những thao tác ở trên có thể dừng các tác vụ Lợi điểm của phương pháp trên sửdụng một đối tượng khác Task để dừng tác vụ Điều này giúp rõ ràng trong việc thựcthi tác vụ Vì tác vụ thực thi khó xác định tác vụ nào xử lý trước nên tách rời thao tácdừng tác vụ giúp rõ ràng cho lập trình viên Đồng thời, có thể dừng một lúc nhiều tác
vụ cùng nhau nếu các tác vụ ấy cùng thông qua một token
Trong vòng lặp sẽ thực hiện dừng vòng lặp khi token gọi Cancel() thông qua câu
lệnh ThrowIfCancellationRequested() của token khi không có tài nguyên nào giải
Trang 6phóng Nếu có tài nguyên cần giải phóng thì sử dụng câu lệnh
token.IsCancellationRequested để kiểm tra trạng thái dừng của token và giải phóng
các tài nguyên cần thiết
Ví dụ 2.3: bên dưới thể hiện dừng nhiều tác vụ cùng lúc
CancellationToken token = tokenSource.Token;
Task task1 = new Task (() => {
for ( int i = 0; i < int MaxValue; i++) {
token.ThrowIfCancellationRequested();
Console WriteLine( "Task 1 - Int value {0}" , i);
}
}, token);
Task task2 = new Task (() => {
for ( int i = 0; i < int MaxValue; i++) {
token.ThrowIfCancellationRequested();
Console WriteLine( "Task 2 - Int value {0}" , i);
}
}, token);
Console WriteLine( "Press enter to start tasks" );
Console WriteLine( "Press enter again to cancel tasks" );
Ngoài ra, để kiểm tra trạng thái tác vụ đang dừng hay chưa có thể dùng lệnh
task.IsCanceled để kiểm tra trạng thái dừng.
Trang 72.1.3 Thực hiện chờ tác vụ
Trong lập trình song song, do đa phần các tác vụ được thực hiện không theo trật tựnhất định (thực hiện song song hóa) nên cần phải có các thao tác chờ đợi tác vụ Cácvấn đề trong việc thực hiện chờ tác vụ bao gồm:
- Chờ một tác vụ riêng rẽ: sử dụng phương thức Wait() của thực thể Task.Phương thức có thể cung cấp một thời gian chờ đợi tối đa và CancellationToken
để dừng việc tác vụ
- Chờ đợi một tập các tác vụ: sử dụng phương thức tĩnh (static)Task.WaitForAll() Phương thức sử dụng một mảng tác vụ cùng với việc cungcấp một thời gian chờ và CancellationToken để dừng tác vụ
- Chờ đợi tác vụ đầu tiên trong tập các tác vụ: sử dụng phương thức tĩnhTask.WaitAny() Phương thức này tương tự như Task.WaitForAll() về cấu trúc
Ví dụ 2.4, bên dưới sẽ minh họa cách chờ một tác vụ riêng rẽ
static void Main( string [] args) {
CancellationTokenSource tokenSource = new
CancellationTokenSource ();
CancellationToken token = tokenSource.Token;
Task task = createTask(token);
Console WriteLine( "Waiting 2 secs for task to complete." );
bool completed = task.Wait(2000);
Console WriteLine( "Wait ended - task completed: {0}" , completed); task = createTask(token);
task.Start();
Console WriteLine( "Waiting 2 secs for task to complete." );
completed = task.Wait(2000, token);
Trang 8Console WriteLine( "Wait ended - task completed: {0} task cancelled {1}" ,
static Task createTask( CancellationToken token) {
return new Task (() => {
for ( int i = 0; i < 5; i++) {
static void Main( string [] args) {
// create the cancellation token source
CancellationTokenSource tokenSource = new
CancellationTokenSource ();
// create the cancellation token
CancellationToken token = tokenSource.Token;
// create the tasks
Task task1 = new Task (() => {
for ( int i = 0; i < 5; i++) {
// check for task cancellation
token.ThrowIfCancellationRequested();
// print out a message
Console WriteLine( "Task 1 - Int value {0}" , i);
// put the task to sleep for 1 second
token.WaitHandle.WaitOne(1000);
}
Console WriteLine( "Task 1 complete" );
}, token);
Trang 9Task task2 = new Task (() => {
Console WriteLine( "Task 2 complete" );
}, token);
// start the tasks
task1.Start();
task2.Start();
// wait for the tasks
Console WriteLine( "Waiting for tasks to complete." );
int taskIndex = Task WaitAny(task1, task2);
Console WriteLine( "Task Completed Index: {0}" , taskIndex);
// wait for input before exiting
Console WriteLine( "Main method complete Press enter to finish." ); Console ReadLine();
như ở trường hợp ThrowIfCancellationRequested() ở phần dừng tác vụ.
Ví dụ 2.6: minh họa bắt ngoại lệ khi chạy tác vụ Trong đó, xử lý ngoại lệ được sửdụng trong một khối lệnh try…catch()
using System;
using System.Threading.Tasks;
namespace Listing_19 {
class Listing_19 {
static void Main( string [] args) {
// create the tasks
Task task1 = new Task (() => {
ArgumentOutOfRangeException exception = new
ArgumentOutOfRangeException ();
exception.Source = "task1" ;
throw exception;
});
Task task2 = new Task (() => {
throw new NullReferenceException ();
});
Task task3 = new Task (() => {
Console WriteLine( "Hello from Task 3" );
});
// start the tasks
Trang 10task1.Start(); task2.Start(); task3.Start();
// wait for all of the tasks to complete
// and wrap the method in a try catch block
try {
Task WaitAll(task1, task2, task3);
} catch ( AggregateException ex) {
// enumerate the exceptions that have been aggregated
foreach ( Exception inner in ex.InnerExceptions) {
Console WriteLine( "Exception type {0} from {1}" ,
inner.GetType(), inner.Source);
}
}
// wait for input before exiting
Console WriteLine( "Main method complete Press enter to finish." ); Console ReadLine();
Các lỗi thường gặp khi lập trình song song cho tác vụ:
- Lỗi deadlock: lỗi khi 2 hay nhiều tác vụ đợi lẫn nhau để hoàn thành tác vụ, điều
đó chiếm giữ tài nguyên làm cho các tác vụ không sử dụng được tài nguyên vàgây ra việc đợi vô thời hạn Hạn chế lỗi deadlock bằng cách hạn chế tác vụ phụthuộc lẫn nhau
- Lỗi biến cục bộ: lỗi nảy sinh khi dùng chung 1 biến giữa các task Ví dụ 2.7 sẽthể hiện rõ gồm 2 phần “bad” task và good task Trong đó, “bad” task sẽ xuấttrùng giá trị I ở mỗi task Còn good task sẽ xuất mỗi task là một giá trị riêngbiệt
using System;
using System.Threading.Tasks;
namespace Local_Variable_Evaluation {
class Local_Variable_Evaluation {
static void Main( string [] args) {
// create and start the "bad" tasks
for ( int i = 0; i < 5; i++)
{
Task Factory.StartNew(() =>
{
// write out a message that uses the loop counter
Console WriteLine( "Task {0} has counter value: {1}" , Task CurrentId, i);
Trang 11});
}
// create and start the "good" tasks
for ( int i = 0; i < 5; i++)
{
Task Factory.StartNew((stateObj) =>
{
// cast the state object to an int
int loopValue = ( int )stateObj;
// write out a message that uses the loop counter
Console WriteLine( "Task {0} has counter value: {1}" , Task CurrentId, loopValue);
}, i);
}
// wait for input before exiting
Console WriteLine( "Main method complete Press enter to
dữ liệu trực tiếp giữa các tác vụ vì làm giảm hiệu năng tính toán khi song song hóa.Đồng thời, đây là một lỗi logic khó được phát hiện khi thực thi chương trình
Tuy nhiên, nếu bắt buộc gặp phải trường hợp chia sẻ dữ liệu giữa các tác vụ thì Net
4 và C sharp cũng cung cấp những phương thức để xử lý nhanh chóng đối với các lậptrình viên
2.2.1 Đồng bộ hóa tác vụ
Trang 12Đồng bộ hóa tác vụ cơ bản là điều chỉnh các lại tác vụ để thực hiện 2 khái niệm cơbản trong đồng bộ hóa tác vụ đó là Critical Region và Synchronization Primitives.
- Critical Region: là khối lệnh chứa một hoặc nhiều câu lệnh dùng để tránh việc
tranh chấp dữ liệu Ví dụ
for (int j = 0; j < 1000; j++) {
account.Balance = account.Balance + 1; // < critical region
}
- Synchronization Primitives: là một kiểu dữ liệu đặc biệt dùng để điều hướng tác
vụ truy cập vào Critical Region để chia sẻ dữ liệu giữa các tiến trình
Các bước cơ bản đồng bộ hóa tác vụ:
- Tác vụ sẽ kiểm tra Synchronization Primitives để biết vùng Critical Regionđang bị chiếm giữ bởi các tác vụ hay chưa
- Nếu chưa thi Critical Region sẽ được cấp phát cho tác vụ để thực thi câu lệnh
- Ngược lại, khi Critical Region đang bị chiếm giữ thì tác vụ yêu cầu sẽ được yêucầu chờ đợi tác vụ đang chiếm giữ Critical Region hoàn thành xong để giảiphóng Critical Region
Các dạng của Synchronization Primitives:
- Lightweight primitives: là những primitive được mở rộng ở .Net 4 so với
các Net trước đây Lợi điểm của primitive này được nâng cao về hiệu năng tínhtoán, có thể đồng bộ hóa trong một miền xác định Primitive này đượcMicrosoft khuyến nghị sử dụng thay cho heavyweight primitives bên dưới bởinăng lực đồng bộ hóa linh động và giảm thiểu xử lý tính toán
- Heavyweight primitives: là những primitive có trước Net4 ra đời, có tính chất
ngược lại Lightweight primitive vì khi thực thi Heavyweight primitives sử dụngnhiều tên miền ứng dụng khác nhau
- Wait handles: là tính năng của hệ điều hành Window dùng để chia sẻ dữ liệu và
điều phối giữa các tác vụ
2.2.2 Các kỹ thuật đồng bộ hóa tác vụ