POSIX Thread (3)
Vậy là chúng ta đã quen với một số hàm cơ bản trong việc tạo và kết thúc thread.
#include <pthread.h>
int pthread_create( pthread_t *restrict thread, const pthread_attr_t *restrict attr,
void *(*start_routine)(void*), void *restrict arg);
void pthread_exit(void *value_ptr);
int pthread_join(pthread_t thread, void **value_ptr);
Một thread được tạo ra sẽ bắt đầu thực hiện đoạn mã trong hàm start_routine() (trong ví dụ trước là hàm thread_function) và kết thúc khi trả về hàm đó. Có thể thấy chúng ta truyền con trỏ hàm (function pointer) tới start_routine vào pthread_create. Tuy nhiên tư tưởng lập trình hướng đối tượng không thích hợp cho những hàm kiểu vậy. Chúng ta thích khởi tạo một thread mới bằng cách tạo ra một instance hay một object của một class nào đó và thực thi bằng cách gọi một member function của object đó. Ví dụ như ta có một class Task, và muốn mỗi đối tượng Task chạy trong một thread mới và thread đó sẽ tự động thực thi member function execute() của đối tượng đó. Nói một mặt nào đó gần như chúng ta muốn tạo ra một function object.
POSIX Thread (2)
Chúng ta hãy bắt đầu bằng một ví dụ đơn giản.
thread1.c
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
void *thread_function(void *arg)
{
int i;
for ( i=0; i<20; i++ ) {
printf("Thread says hi!\n");
sleep(1);
}
return NULL;
}
int main(void)
{
pthread_t mythread;
if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
printf("error creating thread.");
abort();
}
printf(“Waiting for thread to finish...\n”);
if ( pthread_join ( mythread, NULL ) ) {
printf("error joining thread.");
abort();
}
exit(0);
}
Biên dịch chương trình
$ gcc thread1.c -o thread1 -lpthread (more…)
POSIX Thread (1)
Biết cách lập trình với thread và multithread là một trong những kỹ năng cần thiết của một programmer tốt. Trong bài viết này sẽ đề cập về POSIX (Portable Operating System Interface) threads. Như bạn đã biết POSIX (chính xác hơn là chuẩn IEEE 1003.1c của tổ chức IEEE đưa ra) bao gồm những định nghĩa “giao diện” chung cho các hệ điều hành. Điều đó có nghĩa là những hệ điều hành nào support POSIX (GNU/Linux, BSD, Sun Solaris, Unix, …) thì đều có những system call có prototype giống như trong tài liệu về POSIX đưa ra, mặc dù đối với mỗi hệ điều hành có cách implement khác nhau. POSIX threads (Pthreads) là một cách rất tốt để làm tăng độ tin cậy và performance cho chương trình.
Threads cũng tương tự như processes, đều được phân chia thời gian bởi kernel. Với hệ thống chỉ có một bộ vi xử lý thì kernel sử dụng cách phân chia thời gian để “làm cho” các threads như là chạy đồng thời theo cùng cách thức kernel thực hiện với processes. Và với các hệ thống đa nhân thì các threads thực sự có thể chay đồng thời giống như là nhiều processes.
Thế thì tại sao multithread lại được ưa chuộng hơn là nhiều process độc lập đối với các task có mối quan hệ với nhau? Đó là bởi vì các threads sử dụng chung cùng một không gian bộ nhớ. Mỗi thread độc lập đều có thể truy nhập vào cùng một biến toàn cục trong bộ nhớ. Trong khi fork() cho phép tạo ra nhiều process nhưng rất khó khăn trong việc trao đổi thông tin giữa process với nhau vì mỗi process có một không gian vùng nhớ riêng. Không có một câu trả lời đơn giản cho việc trao đổi giữa các process (IPC). Do vậy mà multiprocess programming sẽ phải chịu 2 trở ngại lớn:
- Perforamance thấp vì khi tạo một process mới đòi hỏi kernel thực thi nhiều phép tính toán để cấp phát bộ nhớ.
- Trong hầu hết các trường hợp thì IPC làm chương trình trở nên phức tạp hơn rất nhiều.
Processes and Multi-process Programming (1)
Thực sự khi đọc đi đọc lại bài viết về fork() tôi cảm thấy nó rất là tệ vì quá sơ sài, không làm nổi bật được vai trò của process trong operating system, sự phức tạp của nó cũng như ưu điểm của multi-process programming. Nó đơn giản chỉ là viết về một system call fork() mà thôi, và điểm cơ bản là không thấy được multi-process programming có thể giúp cho hệ thống trở nên mạnh mẽ như thế nào.
Khái niệm trọng tâm trong tất cả các hệ điều hành (operating system) là process (tiến trình). Một process về cơ bản chỉ là một chương trình có thể thực thi. Đi cùng với process là một không gian địa chỉ (address space) – vùng nhớ (từ tối thiểu, thông thường là 0, đến cực đại) mà process có thể đọc và ghi. Không gian địa chỉ này bao gồm đoạn mã thực thi, dữ liệu và stack của nó. Cũng đi cùng với mỗi process là một tập các thanh ghi, bao gồm program counter, stack pointer, các thanh ghi khác và tất cả các thông tin cần thiết để chạy chương trình.
Tất cả các hệ điều hành tiên tiến chạy trên các máy tính cá nhân bây giờ đều là multi-process, có nghĩa là cho phép chạy nhiều process “cùng lúc”. Hệ điều hành sẽ là trung tâm quản lý các process, nó sẽ quyết định khi nào thì dừng một process và start hay tiếp tục một process khác. Khi một process được tạm dừng kiểu này, thì nó sau đó phải được restart tại chính trạng thái mà nó bị dừng. Điều đó có nghĩa là tất cả các thông tin của process phải được lưu ở đâu đó bên ngoài trong lúc tạm dừng. Ví dụ một process có thể đang mở một vài file để đọc. Với mỗi file này thì có một con trỏ chỉ đến vị trí đang đọc trong file. Khi một process bị tạm thời dừng lại, thì tất cả các con trỏ này phải được ghi lại để sau đó các lệnh tiếp theo đối với file đang mở sẽ có được dữ liệu chính xác khi process được tiếp tục. Trong rất nhiều hệ điều hành, các thông tin về mỗi process được lưu vào trong một bảng được gọi là process table (là một mảng hay link-list mà mỗi phần tử là process đang tồn tại). Vì vậy mỗi process bao gồm không gian địa chỉ của nó, thường được gọi là core image, và một entry trong process table mà chứa đựng thanh ghi của nó và những thứ khác.
Những system calls quan trọng nhất trong process management là những lệnh liên quan đến quá trình tạo và huỷ process. Hãy xem xét một ví dụ điển hình: một process gọi command interpreter hay shell để đọc lệnh từ terminal. User gõ lệnh yêu cầu chương trình được dịch, và shell phải tạo ra một process mới mà chạy trình biên dịch. Khi process kết thúc quá trình biên dịch, nó thực hiện một system call để huỷ chính nó.
Nếu một process tạo ra một hay nhiều process khác (thường được gọi là child processes) và những process đó lại có thể tạo ra những process con, chúng ta sẽ có một cấu trúc cây process (process tree structure). Việc trao đổi thông tin giữa các process để đồng bộ các hành động giữa các process được gọi là interprocess communication (IPC) cũng là một vấn đề lớn trong multi-process programming.
Xây dựng ứng dụng multi-process là một công việc khó khăn. Process khi hoạt động phải luôn ở trạng thái tôn trọng và sẵn sàng nhường quyền xử lý CPU cho các process khác ở bất kỳ thời điểm nào, khi hệ thống yêu cầu. Nếu process xây dựng không tốt, thì khi nó đổ vỡ và gây ra lỗi thì có thể làm treo các process khác hay thậm chí phá vỡ hệ điều hành (treo).
fork()
Một trong thứ hay nhất của *NIX mà M$Windows không có chính là system calls: fork(). Fork tạo ra một process con (child process) là copy (nhân bản) của chính process tạo ra nó (parent process) (làm mình nhớ đến clone() để tạo copy một object trong OOP). Để phân biệt giữa parent process và child process chính là giá trị trả về của hàm fork. Ở parent process thì hàm fork sẽ trả về chính là processID của tiến trình con, còn ở child process thì fork sẽ trả về 0. Fork sẽ trả về 2 lần ở tiến trình cha và tiến trình con.
#include <sys/types.h> #include <unistd.h> pid_t fork(void);
leave a comment