Skip to main content

Tìm hiểu về luồng (Thread)

Ngày nay, với sự phát triển với tốc độ chóng mặt của khoa học kỹ thuật, một kỷ nguyên mới được mở ra, kỷ nguyên của công nghệ thông tin. Nhu cầu của loài người ngày càng lớn, đặc biệt là các ngành khoa học kỹ thuật khác đều cần đến sự hổ trợ của công nghệ thông tin, mặc dù công nghệ phần cứng phát triển rất nhanh, CPU với tốc độ xử lý ngày càng cao, nhưng lại nảy sinh nhiều bài toán trong thực tế sản xuất đòi hỏi phải xử lí nhanh hơn nữa.
Vấn đề xử lý song song đang ngày càng được nghiên cứu nhiều để giải quyết một số bài toán mà thực tiễn đang đặt ra, những vấn đề cần có kết quả trong thời gian thực như: bài toán dự báo thời tiết, điều tiết giao thông, điều khiển các con tàu vũ trụ,các bài toán về mô phỏng…Vì vậy, việc nghiên cứu các giải thuật cho xử lý song song là một yêu cầu và là một thách thức cho các nhà khoa học liên quan đến khoa học máy tính. Trong bài viết này mình và các bạn cùng đi tìm hiểu về khái niệm và cơ chế của đa luồng 
  
1. Khái niệm
- Luồng là một cách thông dụng để nâng cao năng lực xử lý của các ứng dụng nhờ vào cơ chế song song.
- Một luồng là một đơn vị cơ bản của việc sử dụng CPU.
- Nó hình thành gồm: một định danh luồng (thread ID), một bộ đếm chương trình, tập thanh ghi và ngăn xếp.
- Nó chia sẻ với các luồng khác thuộc cùng một quá trình một không gian địa chỉ. Nhờ đó các luồng có thể sử dụng các biến toàn cục, chia sẻ các tài nguyên.
- Cách thức các luồng chia sẻ CPU cũng giống như cách thức của các quá trình.
- Một luồng cũng có những trạng thái: đang chạy (running), sẵn sàng (ready), nghẽn (blocked) và kết thúc (dead). Một luồng thì được xem như là một quá trình nhẹ.
Nhờ vào luồng, người ta thiết kế các server có thể đáp ứng nhiều yêu cầu một cách đồng thời.
Các bước tổng quát của một server phục vụ song song
Server phục vụ song song gồm hai phần thực hiện song song nhau:
- Phần 1 ( Dispatcher thread ): Xử lý các yêu cầu kết nối, lặp lại các công việc sau:
+ Lắng nghe yêu cầu kết nối của clients
+ Chấp nhận một yêu cầu kết nối
Tạo kênh giao tiếp ảo mới với clients
Tạo phần 2 để xử lý các thông điệp yêu cầu của clients.
- Phần 2 (Worker Thread): Xử lý các thông điệp yêu cầu từ clients, lặp lại các công việc sau:
+ Chờ nhận thông điệp yêu cầu của clients.
+ Phân tích và xử lý yêu cầu.
+ Gửi thông điệp trả lời cho clients.
Phần 2 sẽ kết thúc khi kênh ảo bị xóa đi.
Với mỗi client, trên server sẽ có một Phần 2 để xử lý yêu cầu của clients. Như vậy tại thời điểm bất kỳ luôn tồn tại một Phần 1 và 0 hoặc nhiều Phần 2
Do phần 2 thực thi song song với phần 1 cho nên nó được thiết kế là một thread
- Nhìn từ góc độ hệ điều hành, luồng có thể được cài đặt ở một trong hai mức:
• Trong không gian người dùng (user space)
• Trong không gian nhân (kernel mode)

2. Luồng ở mức người dùng


 Kiến trúc luồng cài đặt ở mức người dùng
được hỗ trợ dưới nhân và được cài đặt bởi thư viện luồng tại cấp người dùng. Thư viện cung cấp hỗ trợ cho việc tạo luồng, lập thời biểu, và quản lý mà không có sự hỗtrợ từ nhân. Vì nhân không biết các luồng cấp người dùng, tất cả việc tạo luồng và lập thời biểu được thực hiện trong không gian người dùng mà không cần sựcan thiệp của nhân. Do đó, các luồng cấp người dùng thường tạo và quản lý nhanh, tuy nhiên chúng cũng có những trởngại. Thí dụ, nếu nhân là đơn luồng thì bất cứ luồng cấp người dùng thực
hiện một lời gọi hệ thống nghẽn sẽ làm cho toàn bộ quá trình bị nghẽn, thậm chí nếu các luồng khác sẳn dùng để chạy trong ứng dụng. Các thư viện luồng người dùng gồm các luồng POSIX Pthreads, Mach C-threads và Solaris 2 UIthreads.
Không gian người dùng bao gồm một hệ thống runtime mà nó tập hợp những thủ tục quản lý luồng. Các luồng chạy trong không gian nằm bên trên hệ thống runtime thì được quản lý bởi hệ thống này. Hệ thống runtime cũng lưu giữ một bảng tin trạng thái để theo dõi trạng thái hiện hành của mỗi luồng.
Tương ứng với mỗi luồng sẽ có một mục từ trong bảng, bao gồm các thông tin về trạng thái, giá trị thanhghi, độ ưu tiên và các thông tin khác về luồng.
Tiếp cận này có hai mức định thời biểu (Scheduling): bộ định thời biểu cho các quá trình nặng và bộ định  thời biểu trong hệ thống runtime. Bộ lập biểu của hệ thống runtime chia thời gian sử dụng CPU được cấp cho một quá trình thành những khoảng nhỏ hơn để cấp cho các luồng trong quá trình đó. Như vậy việc kết thúc một luồng thì vượt ra ngoài tầm kiểm soát của kernel hệ thống.

3. Luồng ở mức hạt nhân hệ điều hành

Kiến trúc cài đặt ở mức hệ thống
được hỗ trợ trực tiếp bởi hệ điều hành. Nhân thực hiện việc tạo luồng, lập thời biểu, và quản lý khônggian nhân. Vì quản lý luồng được thực hiện bởi hệ điều hành, luồng nhân thường tạo và quản lý chậm hơn luồng người dùng. Tuy nhiên, vì nhân được quản lý các luồng nếu một luồng thực hiện lời gọi hệ thống nghẽn, nhân có thểlập thời biểu một luồng khác trong ứng dụng thực thi. Trong môi trường đa xử lý, nhân có thểlập thời biểu luồng trên một bộxửlý khác. Hầu hết các hệ điều hành hiện nay như Windows NT, Windows 2000, Solaris 2, BeOS và Tru64 UNIX (trước Digital UNIX)-hỗtrợ các luồng nhân. Trong tiếp cận này không có hệ thống runtime và các luồng thì được quản lý bởi kernel của hệ điềuhành. Vì vậy, bảng thông tin trạng thái của tất cả các luồng thì được lưu trữ bởi kernel. Tất cả những lời gọi mà nó làm nghẽn luồng sẽ được bẫy (TRAP) đến kernel. Khi một luồng bị nghẽn, kernel chọn luồng khác cho thực thi. Luồng được chọn có thể cùng một quá trình với luồng bị nghẽn hoặc thuộc một quá
trình khác, vì vậy sự tồn tại của một luồng thì được biết bởi kernel và chỉ có một mức lập biểu trong hệ thống.

4. Các loại cài đặt luồng
4.1 Mô hình nhiều-một
Mô hình nhiều-một ánh xạ nhiều luồng cấp người dùng tới
Mô hình nhiều – một
một luồng cấp nhân. Quản lý luồng được thực hiện trong không gian người dùng vì thế nó hiệu quả nhưng toàn bộquá trình sẽ bị khóa nếu một luồng thực hiện lời gọi hệ thống khóa. Vì chỉ một luồng có thể truy xuất nhân tại một thời điểm nên nhiều luồng không thể chạy song song trên nhiều bộ xử lý. Greenthreads-một thư viện luồng được cài đặt trên các hệ điều hành không hỗ trợ luồng nhân dùng mô hình nhiều-một.

4.2 Mô hình một-một
Mô hình một-một ánh xạ mỗi luồng người dùng tới một luồng nhân. Nó cung cấp khả năng  đồng hành tốt hơn mô hình nhiều-một bằng cách cho một uồng khác chạy khi một luồng thực hiện lời gọi hệ thống nghẽn; nó cũng cho phép nhiều luồng chạy song song trên các bộ xử lý khác nhau. Chỉ có một trở ngại trong mô hình này là tạo luồng người dùng yêu cầu tạo một luồng nhân tương ứng. Vì chi phí cho việc tạo luồng nhân có thể đè nặng lên năng lực thực hiện của ứng dụng, các cài đặt cho mô hình này giới hạn số luồng được hỗ trợ bởi hệ thống. Windows NT, Windows 2000 và OS/2 cài đặt mô hình một-một này.
Mô hình một -  một

4.3 Mô hình nhiều-nhiều
Mô hình nhiều-nhiều đa hợp nhiều luồng cấp người dùng tới số lượng nhỏ hơn hay bằng  các luồng nhân. Số lượng các luồng nhân có thể được xác định hoặc một ứng dụng cụthể hay một máy cụ thể(một ứng dụng có thể được cấp nhiều luồng nhân trên một bộ đa xử lý hơn trên một bộ đơn xử lý). Trong khi mô hình nhiều-một cho phép người phát triển tạo nhiều luồng người dùng như họ muốn, thì đồng hành thật sự là không đạt được vì nhân có thểl ập thời biểu chỉ một luồng tại một thời điểm. Mô hình một-một cho phép đồng hành tốt hơn nhưng người phát triển phải cẩn thận không tạo ra quá nhiều luồng trong một ứng dụng. Mô hình nhiều-nhiều gặp phải một trong hai vấn đề khiếm khuyết: người phát triển có thểtạo nhiều luồng người dùng khi cần thiết và các luồng nhân tương ứng có thểchạy song song trên một bộ đa xử lý. Khi một luồng thực hiện một lời gọi hệ thống  khóa, nhân có thể lập thời biểu một luồng khác thực thi. Solaris 2, IRIX, HP-UX, và Tru64 UNIX hỗ trợ mô hình này.

Mô hình nhiều - nhiều

5. Cấp phát luồng
   5.1 Lời gọi hệ thống fork và exec
Trong chương trước chúng ta mô tả lời gọi hệ thống fork được dùng để tạo một quá trình bản sao  Riêng như thế nào. Trong một chương trình đa luồng, ngữ nghĩa của các lời gọi hệ thống fork và  Exec thay đổi. Nếu một luồng trong lời gọi chương trình fork thì quá trình mới sao chép lại quá trình tất cả luồng hay là một quá trình đơn luồng mới? Một số hệ thống UNIX chọn hai ấn bản fork, một sao chép lại tất cảluồng và một sao chép lại chỉluồng được nạp lên lời gọi hệ thống fork. Lời gọi hệ thống exec  điển hình thực hiện công việc trong cùng một cách như được mô tảtrong chương trước. Nghĩa là, nếu  một luồng nạp lời gọi hệ thống exec, chương trình được xác định trong tham số exec sẽ thay thế toàn bộ quá trình-chứa tất cảluồng và các quá trình tải nhẹ. Việc sử dụng hai ấn bản fork phụthuộc vào ứng  dụng. Nếu exec bịhủy tức thì sau khi phân nhánh (forking) thì sựsao chép lại tất cảluồng là không cần  thiết khi chương trình được xác định trong các tham số exec sẽt hay thế quá trình. Trong trường hợp này, việc sao chép lại chỉ gọi luồng hợp lý. Tuy nhiên, nếu quá trình riêng biệt này không gọi exec sau khi phân nhánh thì quá trình riêng biệt này nên sao chép lại tất cả luồng

  5.2  Sự hủy bỏ luồng
Hủy một luồng là một tác vụ kết thúc một luồng trước khi nó hoàn thành.Thí dụ, nếu nhiều luồng đangtìm kiếm đồng thời thông qua một cơ sở dữ liệu và một luồng trả về kết quả, các luồng còn lại có thể bị hủy.  Một trường hợp khác có thể xảy ra khi người dùng nhấn một nút trên trình duyệt web để dừng trang web đang được tải. Thường một trang web được tải trong một luồng riêng. Khi người dùng nhấn nút stop, luồng đang nạp trang bịhủy bỏ. Một luồng bị hủy thường được xem như luồng đích. Sự hủy bỏ một luồng đích có thểxảy ra hai viễn cảnh khác nhau:
•  Hủy bất đồng bộ: một luồng lập tức kết thúc luồng đích
•  Hủy trì hoãn: luồng đích có thể kiểm tra định kỳ nếu nó sắp kết thúc, cho phép luồng đích một cơ hộitự kết thúc trong một cách có thứ tự. Sựkhó khăn của việc hủy này xảy ra trong những trường hợp khi tài nguyên được cấp phát tới một luồng bị hủy hay một luồng bị hủy trong khi việc cập nhật dữliệu xảy ra giữa chừng, nó đang chia sẻ với các luồng khác. Điều này trởnên đặc biệt khó khăn với sựhủy bất đồng bộ. Hệ điều hành thường đòi lại tài nguyên hệ thống từ luồng bị hủy nhưng thường nó sẽ không
 đòi lại tất cả tài nguyên. Do đó, việc hủy một luồng bất đồng bộ có thể không giải phóng hết tài nguyênhệ thống cần thiết. Một chọn lựa khác, sự hủy trì hoãn thực hiện bằng một luồng báo hiệu rằng một  luồng đích bị này cho phép một luồng kiểm tra nếu nó sẽ bị hủy tại điểm nó có thể an toàn bị hủy. Pthreads gọi những điểm như thế là các điểm hủy (cancellation points). Hầu hết hệ điều hành cho phép một quá trình hay một luồng bị hủy bất đồng bộ. Tuy nhiên, Pthread API cung cấp sự hủy trì hoãn. Điều này có nghĩa rằng một hệ điều hành cài đặt Pthread API sẽ cho phép sự hủy có trì hoãn

6. Nhóm luồng
Trong phần trước, chúng ta mô tả kịch bản đa luồng của một trình phục vụ web. Trong trường hợp này, bất cứ khi nào trình phục vụ nhận một yêu cầu, nó tạo một luồng riêng để phục vụ yêu cầu đó. Ngược lại, tạo một luồng riêng thật sự cao hơn tạo một quá trình riêng, dù sao một trình phục vụ đa luồng có thể phát sinh vấn đề. Quan tâm đầu tiên là lượng thời gian được yêu cầu để tạo luồng trước khi phục vụ yêu cầu, và lượng thời gian xoá luồng khi nó hoàn thành. Vấn đề thứ hai là vấn đề khó giải quyết hơn: nếu chúng ta cho phép tất cả yêu cầu đồng hành được phục vụ trong một luồng mới, chúng ta không thay thế giới hạn trên số lượng luồng hoạt động đồng hành trong hệ thống. Những luồng không giới hạn có thể làm cạn kiệt tài nguyên hệ thống, như thời gian CPU và bộ nhớ. Một giải pháp cho vấn đề này là sử dụng nhóm luồng.
Ý tưởng chung nằm sau nhóm luồng là tạo số lượng luồng tại thời điểm khởi động và đặt chúng vào nhóm, nơi chúng ngồi và chờ công việc. Khi một trình phục vụ nhận một yêu cầu, chúng đánh thức một luồng từ nhóm- nếu một luồng sẳn dùng – truyền nó yêu cầu dịch vụ. Một khi luồng hoàn thành dịch vụ của nó, nó trả về nhóm đang chờ công việc kế. Nếu nhóm không chứa luồng sẳn dùng, trình phục vụ chờ cho tới khi nó rảnh.
Nói cụ thể, các lợi ích của nhóm luồng là:
Thường phục vụ yêu cầu nhanh hơn với luồng đã có hơn là chờ để tạo luồng.
Một nhóm luồng bị giới hạn số lượng luồng tồn tại bất kỳ thời điểm nào. Điều này đặc biệt quan trọng trên những hệ thống không hỗ trợ số lượng lớn các luồng đồng hành.
Số lượng luồng trong nhóm có thể được đặt theo kinh nghiệm (heuristics) dựa trên các yếu tố như số CPU trong hệ thống, lượng bộ nhớ vật lý và số yêu cầu khách hàng đồng hành. Kiến trúc nhóm luồng tinh vi hơn có thể tự điều chỉnh số lượng luồng trong nhóm dựa theo các mẫu sử dụng. Những kiến trúc như thế cung cấp lợi điểm xa hơn của các nhóm luồng nhỏ hơn-do đó tiêu tốn ít bộ nhớ hơn-khi việc nạp trên hệ thống là chậm.
7. Pthreads
Pthreads tham chiếu tới chuẩn POSIX (IEEE 1003.1c) định nghĩa API cho việc tạo và đồng bộ luồng. Đây là một đặc tả cho hành vi luồng không là một cài đặt. Người thiết kế hệ điều hành có thể cài đặt đặc tả trong cách mà họ muốn. Thông thường, các thư viện cài đặt đặc tả Pthread bị giới hạn đối với các hệ thống dựa trên cơ sở của UNIX như Solaris 2. Hệ điều hành Windows thường không hỗ trợ Pthreads mặc dù các ấn bản shareware là sẳn dùng trong phạm vi công cộng.
Trong phần này chúng ta giới thiệu một số Pthread API như một thí dụ cho thư viện luồng cấp người dùng. Chúng ta sẽ xem nó như thư viện cấp người dùng vì không có mối quan hệ khác biệt giữa một luồng được tạo dùng Pthread và luồng được gắn với nhân. Chương trình C hiển thị trong hình dưới đây, mô tả một Pthread API cơ bản để xây dựng một chương trình đa luồng.
Chương trình hiển thị trong hình tạo một luồng riêng xác định tính tổng của một số nguyên không âm. Trong chương trình Pthread, các luồng riêng bắt đầu thực thi trong một hàm xác định. Trong hình, đây là một hàm runner. Khi chương trình này bắt đầu, một luồng riêng điều khiển bắt đầu trong main. Sau khi khởi tạo, main tạo ra luồng thứ hai bắt đầu điều khiển trong hàm runner.
Bây giờ chúng ta sẽ cung cấp tổng quan của chương trình này chi tiết hơn. Tất cả chương trình Pthread phải chứa tập tin tiêu đề pthread.h. pthread_t tid khai báo danh biểu cho luồng sẽ được tạo. Mỗi luồng có một tập các thuộc tính gồm kích thước ngăn xếp và thông tin định thời. Khai báo pthread_attr_t attr hiện diện các thuộc tính cho luồng. Chúng ta sẽ thiết lập các thuộc tính trong gọi hàm pthread_attr_init(&attr). Vì chúng ta không thiết lập rõ thuộc tính, chúng ta sẽ dùng thuộc tính mặc định được cung cấp. Một luồng riêng được tạo với lời gọi hàm pthread_create. Ngoài ra, để truyền định danh của luồng và các thuộc tính cho luồng, chúng ta cũng truyền tên của hàm, nơi một luồng mới sẽ bắt đầu thực thi, trong trường hợp này là hàm runner. Cuối cùng chúng ta sẽ truyền số nguyên được cung cấp tại dòng lệnh, argv[1].
Tại điểm này, chương trình có hai luồng: luồng khởi tạo trong main và luồng thực hiện việc tính tổng trong hàm runner. Sau khi tạo luồng thứ hai, luồng main sẽ chờ cho luồng runner hoàn thành bằng cách gọi hàm pthread_join. Luồng runner sẽ hoàn thành khi nó gọi hàm pthread_exit.
#include<pthread>
#include<stdio.h>
int sum: /*Dữ liệu này được chia sẻ bởi thread(s)*/
void *runner(void *param); /*luồng*/
main(int argc, char *argv[])
{
pthread_t tid; /*định danh của luồng*/
pthread_attr_t attr; /*tập hợp các thuộc tính*/
if(argc !=2){
fprintf(stderr, “usage: a.out <integer value>”);
exit();
}
if (atoi(argv[1] < 0)){
fprintf(stderr,”%d must be >= 0 \n”, atoi(argv[1]));
exit();
}
/*lấy các thuộc tính mặc định*/
pthread_attr_init(&attr);
/*tạo một luồng*/
pthread_create(&tid,&attr,runner, argv[1]);
/*bây giờ chờ luồng kết thúc*/
pthread_join(tid,NULL);
printf(“sum = %d\n”,sum);
/*Luồng sẽ bắt đầu điều khiển trong hàm này*/
void *runner(void *param)
{
int upper = atoi(param);
int i;
sum = 0;
if (upper > 0){
sum+= i;
}
pthread_exit(0);
}
}

8. Luồng Solaris 2
Solaris 2 là một ấn bản của UNIX với hỗ trợ luồng tại cấp độ nhân và cấp độ người dùng, đa xử lý đối xứng (SMP) và định thời thời thực. Solaris 2 cài đặt Pthread API hỗ trợ luồng cấp người dùng với thư viện chứa APIs cho việc tạo và quản lý luồng (được gọi luồng UI). Sự khác nhau giữa hai thư viện này rất lớn, mặc dù hầu hết người phát triển hiện nay chọn thư viện Pthread. Solaris 2 cũng định nghĩa một cấp độ luồng trung gian. Giữa luồng cấp nhân và cấp người dùng là các quá trình nhẹ (lightweight process- LWPs). Mỗi quá trình chứa ít nhất một LWP. Thư viện luồng đa hợp luồng người dùng trên nhóm LWP cho quá trình và chỉ luồng cấp người dùng hiện được nối kết tới một LWP hoàn thành công việc. Các luồng còn lại bị khoá hoặc chờ cho một LWP mà chúng có thể thực thi trên nó.
Luồng cấp nhân chuẩn thực thi tất cả thao tác trong nhân. Mỗi LWP có một luồng cấp nhân, và một số luồng cấp nhân (kernel) chạy trên một phần của nhân và không có LWP kèm theo (thí dụ, một luồng phục vụ yêu cầu đĩa ). Các luồng cấp nhân chỉ là những đối tượng được định thời trong hệ thống. Solaris 2 cài mô hình nhiều-nhiều; toàn bộ hệ thống luồng của nó được mô tả trong hình dưới đây:

Luồng Solaris 2
Các luồng cấp người dùng có thể giới hạn hay không giới hạn. Một luồng cấp người dùng giới hạn được gán vĩnh viễn tới một LWP. Chỉ luồng đó chạy trên LWP và yêu cầu LWP có thể được tận hiến tới một bộ xử lý đơn (xem luồng trái nhất trong hình trên). Liên kết một luồng có ích trong trường hợp yêu cầu thời gian đáp ứng nhanh, như ứng dụng thời thực. Một luồng không giới hạn gán vĩnh viễn tới bất kỳ LWP nào. Tất cả các luồng không giới hạn được đa hợp trong một nhóm cac LWP sẳn dùng cho ứng dụng. Các luồng không giới hạn là mặc định. Solaris 8 cũng cung cấp một thư viện luồng thay đổi mà mặc định chúng liên kết tới tất cả các luồng với một LWP.
Xem xét hệ thống trong hoạt động: bất cứ một quá trình nào có thể có nhiều luồng người dùng. Các luồng cấp người dùng này có thể được định thời và chuyển đổi giữa LWPs bởi thư viện luồng không có sự can thiệp của nhân. Các luồng cấp người dùng cực kỳ hiệu quả vì không có sự hỗ trợ nhân được yêu cầu cho việc tạo hay huỷ, hay thư viện luồng chuyển ngữ cảnh từ luồng người dùng này sang luồng khác.
Mỗi LWP được nối kết tới chính xác một luồng cấp nhân, ngược lại mỗi luồng cấp người dùng là độc lập với nhân. Nhiều LWPs có thể ở trong một quá trình, nhưng chúng được yêu cầu chỉ khi luồng cần giao tiếp với một nhân. Thí dụ, một LWP được yêu cầu mỗi luồng có thể khoá đồng hành trong lời gọi hệ thống. Xem xét năm tập tin khác nhau-đọc các yêu cầu xảy ra cùng một lúc. Sau đó, năm LWPs được yêu cầu vì chúng đang chờ hoàn thành nhập/xuất trong nhân. Nếu một tác vụ chỉ có bốn LWPs thì yêu cầu thứ năm sẽ không phải chờ một trong những LWPs để trả về từ nhân. Bổ sung một LWP thứ sáu sẽ không đạt được gì nếu chỉ có đủ công việc cho năm.
Các luồng nhân được định thời bởi bộ lập thời biểu của nhân và thực thi trên một hay nhiều CPU trong hệ thống. Nếu một luồng nhân khoá (trong khi chờ một thao tác nhập/xuất hoàn thành), thì bộ xử lý rảnh để thực thi luồng nhân khác. Nếu một luồng bị khoá đang chạy trên một phần của LWP thì LWP cũng khoá. Ở trên vòng, luồng cấp người dùng hiện được gán tới LWP cũng bị khoá. Nếu một quá trình có nhiều hơn một LWP thì nhân có thể định thời một LWP khác.
Thư viện luồng tự động thay đổi số lượng LWPs trong nhóm để đảm bảo năng lực thực hiện tốt nhất cho ứng dụng. Thí dụ, nếu tất cả LWPs trong một quá trình bị khoá bởi những luồng có thể chạy thì thư viện tự tạo một LWP khác được gán tới một luồng đang chờ. Do đó, một chương trình được ngăn chặn từ một chương trình khác bởi sự nghèo nàn của những LWPs không bị khoá. LWPs là những tài nguyên nhân đắt để duy trì nếu chúng không được dùng. Thư viện luồng “ages” LWPs và xoá chúng khi chúng không được dùng cho khoảng thời gian dài, điển hình khoảng 5 phút.
Các nhà phát triển dùng những cấu trúc dữ liệu cài đặt luồng trên Solaris 2:
 - Luồng cấp người dùng chứa một luồng ID; tập thanh ghi (gồm một bộ đếm chương trình và con trỏ ngăn xếp); ngăn xếp; và độ ưu tiên (được dùng bởi thư viện cho mục đích định thời). Không có cấu trúc dữ liệu nào là tài nguyên nhân; tất cả chúng tồn tại trong không gian người dùng.
 - Một LWP có một tập thanh ghi cho luồng cấp nhân nó đang chạy cũng như bộ nhớ và thông tin tính toán. Một LWP là một cấu trúc dữ liệu nhân và nó nằm trong không gian nhân
-  Một luồng nhân chỉ có một cấu trúc dữ liệu nhân và ngăn xếp. Cấu trúc dữ liệu gồm bản sao các thanh ghi nhân, con trỏ tới LWP mà nó được gán, độ ưu tiên và thông tin định thời.
Mỗi quá trình trong Solaris 2 gồm nhiều thông tin được mô tả trong khối điều khiển quá trình (Process Control Block-PCB ). Trong thực tế, một quá trình Solaris 2 chứa một định danh quá trình (Process ID-PID); bản đồ bộ nhớ; danh sách các tập tin đang mở, độ ưu tiên; và con trỏ của các luồng nhân vơi quá trình.


Quá trình Solaris 2

Comments

Popular posts from this blog

Ứng dụng giải thuật MiniMax trong trò chơi cờ tướng - Tìm hiểu về trí tuệ nhân tạo (Phần 2)

Chắc hẳn mọi người đều biết về trò chơi thú vị như cờ tướng. Tiếp theo loạt bài về trí tuệ nhân tạo, bài viết này mình sẽ nói về cụ thể giải thuật Minimax ứng dụng trong trò chơi trí tuệ cờ tướng như thế nào. OK! Let's go. 1. Ý tưởng Cờ tướng là trò chơi đối kháng, trong đó hai người luôn phiên nhau đi nước đi của mình. Trạng thái bắt đầu là trạng thái khởi tạo bàn cờ, sau mỗi nước đi của một bên, trạng thái bàn cờ sẽ được thay đổi thành một trạng thái mới hiện hành. Cờ tướng có luật của nó, và trò chơi sẽ kết thúc khi một người có được trạng thái phản ánh sự thắng cuộc hoặc hai người rơi vào trạng thái hòa cờ. Ta tìm cách phân tích xem từ một trạng thái nào đó sẽ dẫn đến đấu thủ nào sẽ thắng với điều kiện cả hai có trình độ như nhau. Giải thuật Minimax sẽ được áp dụng vào trong trò chơi cờ tướng. Hai đấu thủ trong trò chơi sẽ được gọi là MIN và MAX và hai đấu

Sử dụng Jedis làm việc với Redis trong Java

Bài viết này mình sẽ   giới thiệu về Jedis , một thư viện client Java cho  Redis . 1. Tại sao lại là Jedis? Redis liệt kê các thư viện client nổi tiếng nhất trên  trang web chính thức  của họ  .  Có nhiều lựa chọn thay thế cho Jedis, nhưng chỉ có hai lựa chọn khác xứng đáng để đề xuất đó là  lettuce  và  Redisson . Hai clients này có một số tính năng độc đáo như an toàn luồng, xử lý kết nối lại trong suốt và API không đồng bộ, tất cả các tính năng mà Jedis thiếu. Tuy nhiên, Jedis nhỏ và nhanh hơn đáng kể so với hai loại kia.  Bên cạnh đó, nó là thư viện client được lựa chọn của các nhà phát triển Spring Framework, và nó có cộng đồng lớn nhất trong cả ba. 2. Maven Dependencies Hãy bắt đầu bằng cách khai báo dependency trong file  pom.xml  : 1 2 3 4 5 < dependency >      < groupId >redis.clients</ groupId >      < artifactId >jedis</ artifactId >      < version >2.8.1</ version > </ dependency >

Sử dụng Jenkins để Build Docker Images

Khởi chạy Jenkins Khởi chạy Jenkins như một Docker Container với lệnh sau: docker run -d -u root --name jenkins \ -p 8080:8080 -p 50000:50000 \ -v /root/jenkins_2112:/var/jenkins_home \ jenkins/jenkins:2.112-alpine Load Dashboard Tên người dùng  admin có mật khẩu mặc định là  344827fbdbfb40d5aac067c7a07b9230 Trên hệ thống của bạn, bạn có thể tìm mật khẩu bằng docker exec -it jenkins cat /var/jenkins_home/secrets/initialAdminPassword Có thể mất vài giây để Jenkins hoàn thành việc bắt đầu và có sẵn.  Trong các bước tiếp theo, bạn sẽ sử dụng trang Dashboard để định cấu hình các plugin và bắt đầu tạo Image Docker. Cấu hình Plugin Docker Bước đầu tiên là cấu hình  plugin Docker  .  Plugin này dựa trên plugin Jenkins Cloud.  Khi build Docker Image, nó sẽ tạo ra một "Cloud Agent" thông qua plugin.  Tác nhân sẽ là Docker Container được cấu hình để giao tiếp với Docker Daemon Job build của Jenkins sẽ sử dụng vùng chứa nà