Ví dụ đa luồng trên hệ thống cụ thể

Một phần của tài liệu Giáo trình môn Hệ Điều Hành PTIT (Trang 48)

Để dễ hình dung về khái niệm đa luồng, trong phần này sẽ trình bầy ví dụ cụ thể về việc tạo ra luồng trong tiến trình khi lập trình đa luồng trên Java và lập trình đa luồng cho Windows sử dụng ngôn ngữ C++. Đây là các ví dụ đơn giản để tiện cho việc theo dõi và hiểu khái niệm.

a. Đa luồng trong Java

Ngôn ngữ Java được xây dựng với nhiều cơ chế hỗ trợ lập trình đa luồng, bao gồm việc tạo ra, quản lý, đồng bộ hóa các luồng trong tiến trình. Máy ảo Java đóng vai trò ánh xạ các luồng do thư viện ngôn ngữ tạo ra thành các luồng mức nhân (xem khái niệm luồng mức nhân ở phần sau) của hệ điều hành cụ thể, nhờ vậy các chương trình đa luồng viết trên Java có thể chạy trên nhiều hệ điều hành như Windows, Linux, Mac OS, Android.

Dưới đây là ví dụ một chương trình đa luồng viết trên Java (chương trình được viết đơn giản nhất với mục đích minh họa cách tạo ra luồng trong Java).

public class ThreadTest {

public static void main(String [] args) {

MyThread t1 = new MyThread(0, 300, 3); MyThread t2 = new MyThread(1, 300, 3); MyThread t3 = new MyThread(2, 300, 3); t1.start();

t2.start(); t3.start(); }

}

public class MyThread extends Thread {

private int first, last, step;

public MyThread(int s, int n, int m) { this.start = s; this.end = n; this.step = m; }

public void run() {

for(int i = this.first; i < this.last; i+=this.step) {

System.out.println("[ID " +this.getId() + "] " + i); Thread.yield();

} } }

Hình 2.5: Ví dụ chương trình đa luồng đơn giản trên Java

Khi chạy, chương trình này in ra những dòng đầu tiên như sau (lưu ý: thứ tự in ra thực tế có thể thay đổi tùy thuộc vào lần chạy và máy tính cụ thể; ở đây chỉ gồm một phần kết quả được in ra): [ID 8] 0 [ID 9] 1 [ID 10] 2 [ID 8] 3 [ID 9] 4 [ID 8] 6 [ID 10] 5 [ID 9] 7 PTIT

Đoạn chương trình trên hoạt động như sau. Lớp MyThread kế thừa lớp Thread của Java, trong đó quan trọng nhất là MyThread thay thế phương thức run() bằng phương thức run() của mình. Đây là một trong hai cách tiêu chuẩn để tạo ra luồng trong Java. Phương thức run() là điểm vào của luồng.

Trong phương thức main(), chương trình tạo ra ba luồng MyThread theo hai bước: bước 1 là tạo ra ba đối tượng MyThread bằng các lệnh new MyThread, bước là cho các luồng chạy bằng cách gọi phương thức start. Khi gọi start, luồng tương ứng sẽ thực hiện, bắt đầu từ phần mã chứa trong hàm run.

Mỗi luồng trong ba luồng vừa được tạo ra in ra các số trong khoảng từ first tới last với bước nhẩy step cùng với số định danh của luồng được trả về bằng phương thức getID() của lớp Thread. Do ba luồng bắt đầu với giá trị first khác nhau nên luồng thứ nhất sẽ in các số 0, 3, 6, …, luồng thứ 2 in các số 1, 4, 7, …, và luồng thứ ba in ra 2, 5, 8, … Khi ba luồng chạy đồng thời, kết quả in ra của ba luồng sẽ trộn vào nhau cho kết quả như trên. Phương thức Thread.yield() được thêm vào để không luồng nào chạy trong thời gian quá lâu.

Bên cạnh cách tạo ra luồng bằng cách kế thừa class Thread và phủ quyết phương thức run() như ở trên, một kỹ thuật thông dụng nữa để tạo ra luồng trong Java là tạo ra lớp để triển khai giao diện Runnable và phương thức run() của giao diện này. Giao diện Runnable được định nghĩa như sau:

public interface Runnable {

public abstract void run();

}

b. Ví dụ đa luồng trong Windows

Windows là hệ điều hành hỗ trợ đa luồng. Giao diện lập trình Windows API chứa các hàm cho phép tạo ra và xác định tham số quản lý luồng. Ngoài ra, Windows API còn cung cấp các công cụ đồng bộ luồng.

Dưới đây là ví dụ chương trình tạo ra nhiều luồng sử dụng hàm CreateThread do Windows API cung cấp. Chương trình này có nội dung tương tự chương trình ví dụ đa luồng cho Java ở phần trên, trong đó tiến trình tạo ra ba luồng chạy đồng thời, mỗi luồng in ra các số trong khoảng từ start tới end. Chương trình được viết một cách đơn giản nhất để người đọc dễ theo dõi cách tạo ra luồng, và do vậy thiếu các phần về xử lý lỗi cũng như các chức năng nâng cao liên quan tới quản lý luồng trong Windows.

#include <windows.h> #include <stdio.h> struct thread_data { int id; int start; int end; int step;

thread_data(int _id, int _start, int _end, int _step) : PTIT

id(_id), start(_start), end(_end), step(_step) {} };

DWORD WINAPI thread_func(LPVOID lpParameter) {

thread_data *td = (thread_data*)lpParameter;

for (int i = td->start; i < td->end; i+= td->step){ printf("\n[id = %d]: %d", td->id, i);

Sleep(1); }

return 0; }

void main(int argc, char ** argv) { CreateThread(NULL, 0, thread_func, new thread_data(0,0,300,3),0,0); CreateThread(NULL, 0, thread_func, new thread_data(1,1,300,3),0,0); CreateThread(NULL, 0, thread_func, new thread_data(2,2,300,3),0,0); getchar(); }

Hình 2.6. Ví dụ chương trình đa luồng đơn giản trên Windows

Khi chạy, một phần kết quả được in ra có dạng như bên dưới (thứ tự in ra có thể thay đổi tùy vào lần chạy và hệ thống cụ thể; kết quả đầy đủ phải gồm các số nhỏ hơn 300).

[id = 0]: 0 [id = 2]: 2 [id = 1]: 1 [id = 0]: 3 [id = 1]: 4 [id = 2]: 5 [id = 1]: 7 [id = 0]: 6 [id = 2]: 8 [id = 0]: 9 [id = 1]: 10

Chương trình làm việc như sau. Từ hàm main(), tiến trình chính gọi hàm CreateThread ba lần để sinh ra ba luồng. Trong các tham số của hàm CreateThread có con trỏ tới hàm thread_func, là điểm bắt đầu của luồng tương ứng. Người lập trình có thể thay đổi hàm này để chạy các luồng khác nhau theo ý muốn. Một tham số khác của CreateThread chứa con trỏ tới tham số cần truyền cho luồng, ở đây là con trỏ tới cấu trúc dữ liệu thread_data. Việc sử dụng cấu trúc này là cần thiết vì ở đây cần truyền cho mỗi luồng bốn tham số. Cấu trúc thread_data được khai báo với ba tham số start, end, step là bắt đầu, kết thúc, và bước nhẩy cho vòng lặp, ngoài ra còn chứa thêm số định danh id để dễ phân biệt kết quả của mỗi luồng. Mỗi luồng khi chạy sẽ in ra id của mình cùng với các số trong khoảng start – end với cùng bước nhẩy là 3.

Lưu ý, trong chương trình trên, hàm Sleep() được thêm vào để các luồng có khoảng dừng nhỏ, nhờ vậy các luồng khác có thể chạy xen kẽ. Hàm Sleep() ở đây đóng vai trò tương tự Thread.yield() trong ví dụ với Java. Ngoài ra, thông thường luồng chính bắt đầu từ hàm main() phải chờ cho tới khi các luồng con kết thúc. Để cho đơn giản, ở đây sử dụng hàm getchar(). Người dùng sẽ đợi tới khi các luồng con in xong kết quả và kết thúc mới bấm phím để kết thúc luồng chính.

Một phần của tài liệu Giáo trình môn Hệ Điều Hành PTIT (Trang 48)

Tải bản đầy đủ (PDF)

(183 trang)