Ví dụ: - Một trình duyệt Web có thể sử dụng một thread để thực hiện việc hiển thị hình ảnh hoặc nội dung ra màn hình, một thread khác thực hiện việc nhận các thông tin gởi xuống từ serve
Trang 1Báo cáo tiểu luận
Trang 2Mục lục
I Overview – Tổng quan 3
1 Motivation – tại sao sử dụng multithread 3
2 Benefits – Các lợi ích khi sử dụng multithread 4
II Multithreading Model – Các mô hình ứng dụng multithread 5
1 Mô hình Many-to-one 5
2 Mô hình One-to-one 6
3 Mô hình Many-to-many 7
III Thread Library – thư viện lập trình thread 9
1 Pthreads 9
2 Win32 Threads 11
3 Java Threads 12
IV Thread issues – Các vấn đề liên quan đến sử dụng multithread 15
1 The fork() and exec() system call – Lời gọi hệ thống 15
2 Cancellation – Hủy một thread 16
3 Signal Handling – Xử lí tín hiệu 16
4 Thread Pools 18
5 Thread-specific Data – Thông tin xác định Thread 20
6 Scheduler Activation – Kích hoạt bộ định thời 20
Phụ lục 23
Trang 4Thread
I Overview – Tổng quan
Thread là đơn vị cơ bản sử dụng các tài nguyên của CPU để thực thi một công việc Một thread bao gồm thread ID, program counter, tập thanh ghi và stack Các thread trong cùng một process sẽ dùng chung các tài nguyên của hệ điều hành, và vì nó cùng thuộc về một process nên tất nhiên nó cùng chia sẽ phần code section và data section của process đó Trước đây, một process truyền thống (hay còn gọi là heavyweight process) chỉ có thể điều khiển một thread duy nhất Nếu một process có thể điều khiển nhiều thread, nó có thể làm được nhiều công việc hơn trong cùng một thời điểm Hình ảnh dưới đây sẽ mô tả sự khác nhau giữa một heavyweight process chỉ sử dụng một thread so với một multithreaded process với nhiều luồng xử lí cùng lúc
Tác dụng của multithread: đáp ứng nhanh đối với người dùng, chia sẻ tài nguyên trong quá trình, tính kinh tế, và khả năng thuận lợi trong kiến trúc đa xử lý
1 Motivation – tại sao nên sử dụng multithread
Trang 5Đa số các phần mềm chạy trên các thế hệ máy tính hiện đại ngày nay đều được thiết kế theo hướng multithread Thông thường, một ứng dụng sẽ chỉ có một process nhưng lại điều khiển nhiều thread cùng một lúc
Ví dụ:
- Một trình duyệt Web có thể sử dụng một thread để thực hiện việc hiển thị hình ảnh hoặc nội dung ra màn hình, một thread khác thực hiện việc nhận các thông tin gởi xuống từ server thông qua mạng internet
- Một trình soạn thảo văn bản có thể có một thread dùng để hiển thị giao diện ra màn hình, một thread khác sẽ nhận các phím do người dùng nhập vào từ bàn phím, và một thread nữa dùng để kiểm tra các lỗi ngữ pháp… từ background
Trong một số tình huống nhất định, một ứng dụng yêu cầu phải giải quyết nhiều công việc giống nhau tại cùng một thời điểm Ví dụ, một web server cần phải đáp ứng các yêu cầu từ phía máy khách (clients) để hiển thị nội dung của trang web (hình ảnh, video, sound, text…) Xét trường hợp có đến hàng nghìn request từ clients gởi đến server cùng một lúc, nếu server này chỉ sử dụng single-thread process, nó chỉ có thể phục vụ cho một request tại một thời điểm mà thôi, và như vậy thời gian mà một client phải đợi để được đáp ứng sẽ rất lớn
Một giải pháp được đưa ra đó là – server này sẽ sử dụng các process riêng biệt để đáp ứng yêu cầu từ máy khách, mỗi process được tạo ra sẽ chỉ phục vụ cho một client duy nhất Thực tế, trước khi multithreaded process trở nên thông dụng thì đây là một giải pháp được sử dụng khá nhiều Tuy nhiên, như chúng ta đã biết, việc tạo ra một process thường tiêu tốn nhiều thời gian cũng như tài nguyên, và nếu một process mới tạo ra cũng thực thi những công việc tương tự như các process đã tạo ra từ trước thì sự lãng phí sẽ trở nên rất lớn Một giải pháp mới hiệu quả hơn được đưa ra đó là sử dụng nhiều thread trong một process Với giải pháp này, web server sẽ lắng nghe các yêu cầu từ phía người dùng trên các thread riêng biệt, và nếu request được đáp ứng, nó sẽ tạo ra một thread khác để thực hiện các yêu cầu thay vì tạo ra một process khác
Thread cũng đóng một vai trò quan trọng trong hệ thống sử dung lời gọi thủ tục xa - RPCs (Remote Procedure Call system) RPCs cho phép giao tiếp liên quá trình bằng cách cung cấp cơ chế giao tiếp tương tự như các lời gọi hàm hay thủ tục thông thường Thông thường, các RPC server đều hỗ trợ multithread Khi một server nhận một thông điệp (message), nó sẽ đáp ứng các yêu cầu của message đó bằng một thread riêng biệt Nhờ đó, server có thể đáp ứng nhiều yêu cầu được gởi đến vào cùng một lúc
Hiện nay, hầu hết các hệ điều hành đều hỗ trợ multithread, một số thread hoạt động ngay ở trong nhân, và mỗi thread sẽ thể hiện một công việc riêng biệt, có thể là các công việc như quản lí các thiết bị hoặc xử lí interrupt Ví dụ, Solaris tạo ra một tập hợp các thread trong nhân dùng cho việc xử lí interrupt, còn Linux lại dùng kernel thread để quản lí lượng bộ nhớ trống trong hệ thống
2 Benefits – Các lợi ích khi sử dụng multithread
Trang 6Các lợi ích của việc lập trình ứng dụng multithread được đề cấp đến trong 4 vấn đề chính:
- Khả năng đáp ứng: việc xây dựng một ứng dụng tương tác sử dụng nhiều luồng xử lí cho phép chương trình có thể tiếp tục chạy ngay cả khi một phần công việc nào đó của
nó bị block hoặc khi chương trình đang thực hiện một thao tác nào đó cần rất nhiều thời gian xử lí, nhờ đó tăng khả năng đáp ứng (responsiveness) cho người dùng Thí dụ, một multithreaded web browser có thể cho phép người dùng tương tác với nó bằng một thread, trong khi nó sẽ thực hiện việc load một hình ảnh từ server bằng một thread khác
- Chia sẻ tài nguyên: Mặc định, các thread chia sẻ bộ nhớ và các tài nguyên của process
đã tạo ra chúng Thuận lợi của việc dùng chung code và data đó là nó cho phép một ứng dụng có thể tạo ra nhiều thread thực hiện các công việc khác nhau trong cùng một địa chỉ ô nhớ
- Kinh tế: Việc cấp phát bộ nhớ và tài nguyên cho việc khởi tạo một process thường tốn khá nhiều chi phí Bởi vì các thread dùng chung tài nguyên của process tạo ra nó, vì vậy
sẽ tiết kiệm hơn trong việc khởi tạo cũng như chuyển đổi trạng thái (context-switch) của process Thực tế mà nói thì việc so sánh sự khác nhau về chi phí giữa việc sử dụng process hay thread thường rất khó khăn, nhưng thông thường thì việc tạo và quản lí process tốn thời gian hơn so với việc tạo ra một thread Một ví dụ điển hình đó là trong Solaris, việc tạo ra một process sẽ chậm hơn 30 lần so với tạo ra một thread, và thời gian để thực hiện việc chuyển đổi trạng thái cũng chậm hơn khoảng 5 lần
- Tận dụng kiến trúc đa nhân (multiprocessor architectures): những lợi ích của multithread được tăng lên rất nhiều trong kiến trúc multiprocessor, ở đó các thread có thể thực thi song song trên các processor khác nhau Trong khi một process sử dụng single thread chỉ có thể chạy trên một CPU, nó không quan tâm đến việc hệ thống đó có bao nhiêu CPU, do đó không tận dụng được hết sức mạng của hệ thống có nhiều chip xử
lí đang trở nên phổ biến hiện nay
II Multithreading Model – Các mô hình ứng dụng multithread
Ở phần một, chúng ta đã tìm hiểu một cách khái quát về thread Tuy nhiên, trong hệ thống,
thread được chia ra thành 2 loại tùy theo mức độ sử dụng Đó là user thread – các thread được cung cấp ở mức độ người dùng – user level, hoặc kernel threads – được cung cấp bởi
kernel User thread đứng trên kernel và không chịu sự quản lí của kernel, còn kernel threads được quản lí và hỗ trợ trực tiếp bởi hệ điều hành Hầu như tất cả các hệ điều hành hiện nay, bao gồm Windows XP, Windows Vista, Linux, MAC OS X, Solaris, và Tru64 UNIX (trước đây là Digital UNIX) đều hỗ trợ kernel-thread
Tất nhiên, giữa user threads và kernel threads sẽ phải tồn tại một mối liên hệ nhất định Có
3 mô hình phổ biến để thể hiện mối quan hệ này, và nội dung chính của phần này chính là tìm hiểu cách thiết lập mối quan hệ của 3 loại mô hình đó
1 Mô hình Many-to-one
Trang 7Mô hình Many-to-One kết nối nhiều thread ở cấp độ người dùng (user level) vào cùng một kernel thread Việc quản lí thread được thực hiện một cách hiệu quả bởi thread library trong khu vực người dùng (user space) Tuy nhiên, nhược điểm của mô hình này
là toàn bộ quá trình đang chạy sẽ bị block nếu một trong các thread thuộc cấp độ người dùng thực hiện lời gọi hệ thống block (system call dùng để tạm dừng hoạt động) Ngoài
ra, vì chỉ có thể có duy nhất một thread có thể truy cập vào kernel vào một thời điểm,
do đó không thể chạy song song nhiều thread trong hệ thống đa nhân - multiprocessors
Ví dụ điển hình của thư viện thread sử dụng model này là Green threads dùng trong Solaris và GNU Portable Threads
Mô hình Many-to-One
2 Mô hình One-to-one
Mô hình One-to-One liên kết mỗi user-thread với một kernel-thread Mô hình này cung cấp giải pháp để giải quyết hạn chế của mô hình Many-to-One bằng cách cho phép một user-thread khác vẫn tiếp tục được thực thi khi có một thread thực hiện blocking system call Hơn nữa, mô hình này cho phép nhiều threads được phép thực thi song song trong
hệ thống multiprocessor Điều hạn chế duy nhất của mô hình này là để tạo ra một thread yêu cầu phải tạo ra một kernel-thread tương đương Bởi vì chi phí dành cho việc tạo ra một kernel-thread có thể cản trở hiệu suất làm việc của ứng dụng nên hầu hết các thư viện, hệ thống sử dụng mô hình này đều giới hạn một số lượng threads nhất định Linux, cùng với dòng hệ điều hành Windows - bao gồm Windows 95, Windows 98, NT,
user-2000 và Windows XP đều sử dụng mô hình one-to-one
Trang 8Mô hình One-to-One
3 Mô hình Many-to-many
Mô hình Many-to-Many sẽ tạo ra mối liên kết giữa nhiều user-level threads với một số kernel threads, trong đó số lượng của kernel threads không vượt quá user threads (xem hình) Số lượng các kernel-thread được xác định tùy theo loại ứng dụng hoặc loại máy (Ứng dụng chạy trên hệ thống đa nhân có thể được cấp phát nhiều kernel-threads hơn
so với hệ thống đơn nhân) Trong khi mô hình many-to-one cho phép developer tạo ra bao nhiêu user threads tùy thích, nhưng việc thực thi đồng thời giữa các thread vẫn không thể, bởi vì kernel chỉ có thể sắp xếp (schedule) cho một thread tại một thời điểm
mà thôi Còn đối với one-to-one model, các thread có thể thực thi song song, tuy nhiên người phát triển cần phải lưu ý đến việc không được phép tạo ra quá nhiều threads trong một ứng dụng (và trong một số trường hợp số lượng thread được phép tạo ra sẽ
bị giới hạn) Mô hình many-to-many khắc phục cả 2 nhược điểm của 2 mô hình trên: developers có thể tạo ra một số lượng user threads cần thiết vừa đủ, và kernel thread tương ứng có thể thực thi song song trong hệ thống multiprocessor Ngoài ra, khi một thread thực hiện một blocking system call, kernel vẫn có thể schedule cho một thread
khác bắt đầu thực thi
Trang 9Mô hình Many-to-Many
Một biến thể thông dụng của mô hình Many-to-Many - bên cạnh cơ chế thiết lập mối liên hệ giữa kernel threads và user threads giống như mô hình many-to-many, biến thể này còn cho phép một user thread nối với một kernel thread – gọi là mô hình two-level
Mô hình này được hỗ trợ bởi các hệ điều hành như IRIX, HP-UX và Tru64 UNIX Hệ điều hành Solaris hỗ chỉ hỗ trợ model này trong các phiên bản ra đời trước bản Solaris 9, từ phiên bản thứ 9 trở đi, Solaris chuyển sang sử dụng hô hình one-to-one
Mô hình Two-Level
Trang 10III Thread Library – thư viện lập trình thread
Một thread library sẽ cung cấp cho lập trình viên một giao diện API, hỗ trợ việc khởi tạo và quản lí các thread Có 2 cách để cài đặt một thread library
- Cách 1: Cung cấp một thư viện hoàn chỉnh trong khu vực người dùng, không hỗ trợ các kernel threads Tất cả các code và data structures dùng cho library đều nằm trong user space Điều này nghĩa là việc gọi một hàm từ library sẽ cho kết quả là một local function call trong user space chứ không phải là system call
- Cách 2: cài đặt một thư viện cung cấp API để thao tác với các thread ở kernel-level và được hỗ trợ trực tiếp bởi hệ điều hành Trong trường hợp này, code và data structures dùng trong library tồn tại trong kernel space Gọi một hàm trong API sẽ thực hiện một system call đến kernel Hiện nay, 3 loại thread library chủ yếu được sử dụng gồm có: (1) POSIX Pthreads, (2) Win32, và (3) Java Pthreads – POSIX thread có thể cung cấp cả user hoặc kernel-level library Win32 thread library
là một thư viện cung cấp các kernel-level thread dùng trên các hệ thống sử dụng Windows Java thread API cho phép khởi tạo thread và quản lí trực tiếp trong chương trình Java Thông thường, trên hệ thống sử dụng Windows, Java threads sẽ sử dụng Win32 API, và Pthreads đối với hệ thống UNIX hoặc Linux
Trong phần còn lại của section này sẽ trình bày cách tạo một thread sử dụng lần lượt 3 thư viện kể trên Và để dễ hiểu, chúng ta sẽ làm một ví dụ về chương trình multithread tính tổng của các số nguyên không âm
1 Pthreads
Pthreads là một phần của POSIX standard (IEEE 1003.1c) định nghĩa một giao diện lập trình ứng
dụng (API) cho việc tạo và đồng bộ các threads Đây là một thư viện cung cấp các mô tả chi tiết (specification) hoạt động của thread theo ý muốn người dùng, không phải là một thư viện cung cấp cho người dùng giao diện cài đặt (implementation) Những người phát triển hệ điều hành có
thể sử dụng mô tả này theo bất kì cách nào họ muốn Nhiều hệ thống sử dụng Pthreads bao gồm Solaris, Linux, Mac OS X và Tru64 UNIX
Đoạn chương trình sử dụng ngôn ngữ C dưới đây sẽ trình bày cách tạo một chương trình
multithread sử dụng Pthreads API với chức năng tính tổng các số nguyên không âm trong một thread riêng biệt Trong một chương trình sử dụng Pthreads, các threads riêng biệt bắt đầu việc thực thi trong một hàm xác định, trong ví dụ này chính là hàm runner() Khi chương trình này khởi chạy, một thread điều khiển chính được tạo ra bởi hàm main() Sau đó, một thread thứ 2 sẽ được tạo ra bởi hàm runner() Cả 2 thread này đều dùng chung một biến toàn cục sum
#include <pthread.h>
#include <stdio.h>
int sum; /* this data is shared by the thread(s) */
void *runner(void *param); /*the thread*/
Trang 11int main(int argc, char *argv[])
{
pthread_t tid; /* the thread identifier */
pthread_attr_t attr; /* set of thread attributes */
if (argc != 2) {
fprintf (stderr, "usage: a.out <integer value>\n");
return -1;
}
if (atoi(argv[1] < 0) {
fprintf (stderr, "%d must be >= 0\n", atoi(argv[1]));
return -1;
}
/* get the default attributes */
pthread_attr_init (&attr);
/* create the thread */
pthread_create (&tid, &attr, runner, argv[1]);
/* wait for the thread to exit */
pthread_join(tid, NULL);
printf ("sum = %d\n", sum);
}
/* The thread will begin control in this function */
void *runner(void *param)
Trang 12gán các thuộc tính này bằng lời gọi hàm pthread_attr_init(&attr) Bởi vì chúng ta không thể trực tiếp set giá trị của bất kì thuộc tính nào, do đó chúng ta sử dụng các thuộc tính mặc định Một thread mới được tạo ra với lời gọi hàm pthread_create() Các tham số cần thiết của hàm này bao gồm tên thread, các thuộc tính và tên hàm mà thread thực thi – trong trường hợp này là hàm runner(), cuối cùng là một giá trị số nguyên được nhập vào bằng command line, argv[1]
Lúc này, chương trình có 2 thread chạy cùng lúc: thread khởi tạo (initial thread) được tạo ra từ hàm main() – parent thread và một thread con được khởi tạo bên trong hàm main thực thi hàm runner() Sau khi thread con được tạo ra, thread cha sẽ đợi cho đến khi thread này hoàn tất công việc của nó bằng hàm pthread_join() Thread dùng để tính tổng sẽ kết thúc khi nó gọi đến hàm pthread_exit() Sau khi thread con trả về kết quả, parent thread sẽ thực hiện việc đưa kết quả ra màn hình
DWORD Sum; /* data is shared by the theads(s) */
/* the thread runs in this separate function */
DWORD WINAPI Summation (LPVOID Param)
{
DWORD Upper = *(DWORD*)Param;
for (DWORD i = 0; i <= Upper; i++)
fprintf (stderr, "An integer parameter is required\n"); return -1;
} Param = atoi(argv[1]);