Một giải pháp loại trừ tương hỗ khác không phụ thuộc vào sự hỗ trợ của phần cứng (dưới dạng các lệnh kiểm tra và xác lập trình bày ở trên), đồng thời tương đối dễ sử dụng là
cờ hiệu hay đèn hiệu (semaphore) do Dijkstra đề xuất.
Cờ hiệu S là một biến nguyên được khởi tạo một giá trị ban đầu nào đó, bằng khả năng phục vụ đồng thời của tài nguyên. Trừ thao tác khởi tạo, giá trị của cờ hiệu S chỉ có thể thay đổi nhờ gọi hai thao tác là Wait và Signal. Các tài liệu trước đây sử dụng ký hiệu P - viết tắt cho từ “kiểm tra” trong tiếng Đức - cho thao tác Wait, và V - viết tắt của từ “tăng” trong tiếng Đức - cho thao tác Signal. Hai thao tác này có ý nghĩa như sau:
- Wait(S): Giảm S đi một đơn vị. Nếu giá trị của S âm sau khi giảm thì tiến trình gọi thao tác P(S) sẽ bị phong tỏa (blocked). Nếu giá trị của S không âm, tiến trình sẽ được thực hiện tiếp.
- Signal(S): Tăng S lên một đơn vị. Nếu giá trị S nhỏ hơn hoặc bằng 0 sau khi tăng thì một trong các tiến trình đang bị phong tỏa (nếu có) sẽ được giải phóng và có thể thực hiện tiếp.
Điểm đầu tiên cần lưu ý là hai thao tác Wait và Signal là những thao tác nguyên tử, không bị phân chia. Trong thời gian tiến trình thực hiện thao tác như vậy để thay đổi giá trị cờ hiệu, thao tác sẽ không bị ngắt giữa chừng.
Khi tiến trình bị phong tỏa, tiến trình sẽ chuyển sang trạng thái chờ đợi cho đến khi hết bị phong tỏa mới được phép thực hiện tiếp. Các tiến trình bị phong tỏa được xếp vào hàng đợi của cờ hiệu.
Xây dựng cờ hiệu
Cờ hiệu có thể được xây dựng dưới dạng một cấu trúc trên ngôn ngữ C với hai thao tác Wait và Signal như sau:
struct semaphore { int value;
process *queue;//danh sách chứa các tiến trình bị phong tỏa };
void Wait(semaphore& S) {
S.value--;
if (S.value < 0) {
Thêm tiến trình gọi Wait vào S.queue block(); //phong tỏa tiến trình } } void Signal(semaphore& S) { S.value++; if (S.value <= 0) {
Lấy một tiến trình P từ S.queue wakeup(P);
} }
Hình 2.14. Định nghĩa cờ hiệu trên C
Mỗi cờ hiệu có một giá trị và một danh sách queue chứa tiến trình bị phong tỏa. Thao tác block() trong Wait phong tỏa tiến trình gọi Wait và thao tác wakeup() trong Signal khôi phục tiến trình phong tỏa về trạng thái sẵn sàng. Hai thao tác block và wakeup được thực hiện nhờ những lời gọi hệ thống của hệ điều hành.
Danh sách tiến trình bị phong tỏa queue có thể xây dựng bằng những cách khác nhau, chẳng hạn dưới dạng danh sách kết nối các PCB của tiến trình. Việc chọn một tiến trình từ danh sách khi thực hiện Signal có thể thực hiện theo nguyên tắc FIFO hoặc theo những thứ tự khác. Cần lưu ý rằng việc sử dụng FIFO sẽ đảm bảo điều kiện chờ đợi có giới hạn, tức là tiến trình chỉ phải chờ đợi một thời gian giới hạn trước khi vào đoạn nguy hiểm.
Cách sử dụng cờ hiệu
Cờ hiệu được tiến trình sử dụng để gửi tín hiệu trước khi vào đoạn nguy hiểm và sau khi ra khỏi đoạn nguy hiểm. Đầu tiên, cờ hiệu được khởi tạo một giá trị dương hoặc bằng không. Mỗi cờ hiệu với giá trị đầu dương thường dùng để kiểm soát việc truy cập một tài nguyên với
khả năng phục vụ đồng thời một số lượng hữu hạn tiến trình. Ví dụ, tại mỗi thời điểm tài nguyên như máy in chỉ cho phép một tiến trình ghi thông tin, và cờ hiệu dùng cho máy in được khởi tạo bằng 1.
Khi tiến trình cần truy cập tài nguyên, tiến trình thực hiện thao tác Wait của cờ hiệu tương ứng. Nếu giá trị cờ hiệu âm sau khi giảm có nghĩa là tài nguyên được sử dụng hết khả năng và tại thời điểm đó không phục vụ thêm được nữa. Do vậy, tiến trình thực hiện Wait sẽ bị phong tỏa cho đến khi tài nguyên được giải phóng. Nếu tiến trình khác thực hiện Wait trên cờ hiệu, giá trị cờ hiệu sẽ giảm tiếp. Giá trị tuyệt đối của cờ hiệu âm tương ứng với số tiến trình bị phong tỏa.
Sau khi dùng xong tài nguyên, tiến trình thực hiện thao tác Signal trên cùng cờ hiệu. Thao tác này tăng giá trị cờ hiệu và cho phép một tiến trình đang phong tỏa được thực hiện tiếp.
Nhờ việc phong tỏa các tiến trình chưa được vào đoạn nguy hiểm, việc sử dụng cờ hiệu tránh cho tiến trình không phải chờ đợi tích cực và do vậy tiết kiệm được thời gian sử dụng CPU.
Loại trừ tương hỗ được thực hiện bằng cách sử dụng cờ hiệu như thể hiện trên hình 2.15.
…
const int n; //n là số lượng tiến trình semaphore S = 1;
void P(int i){ //tiến trình P(i) for(;;){ //lặp vô hạn
Wait(S);
<Đoạn nguy hiểm> Signal(S);
<Phần còn lại của tiến trình> }
}
void main(){
//tắt tiến trình chính, chạy đồng thời n tiến trình StartProcess(P(1));
...
StartProcess(P(n)); }
Hình 2.15. Loại trừ tương hỗ sử dụng cờ hiệu