CHƢƠNG 5 ĐỒNG BỘ TIẾN TRèNH
5.3 Giải phỏp cho cỏc tiến trớnh
5.3.1 Giải phỏp phần mềm
5.3.1.1 Cờ hiệu (semaphore)
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:
…
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)); }
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 28
… bool flag[2]; int turn; void P0(){ //tiến trỡnh P0 for(;;){ //lặp vụ hạn flag[0]=tru e; turn=1;
while(flag[1] && turn==1);//lặp đến khi điều kiện khụng thỏa
<Đoạn nguy hiểm>
flag[0]=false;
<Phần cũn lại của tiến trỡnh>
5.3.1.2 Giải thuật Peterson
Giải thuật Peterson do Gary Peterson để xuất năm 1981 cho bài toỏn đoạn nguy hiểm. Cựng với giải thuật Dekker, giải thuật Peterson là giải phỏp thuộc nhúm phần mềm, tức là giải phỏp khụng đũi hỏi sự hỗ trợ từ phớa phần cứng hay hệ điều hành. So với giải thuật Dekker, giải thuật Peterson dễ hiểu hơn và được trỡnh bày ở đõy để đại diện cho nhúm giải phỏp phần mềm.
Giải thuật Peterson được đề xuất ban đầu cho bài toỏn đồng bộ hai tiến trỡnh. Giả sử cú hai tiến trỡnh P0 và P1 thực hiện đồng thời với một tài nguyờn chung và một đoạn nguy hiểm chung. Mỗi tiến trỡnh thực hiện vụ hạn và xen kẽ giữa đoạn nguy hiểm với phần cũn lại của tiến trỡnh.
Giải thuật Peterson yờu cầu hai tiến trỡnh trao đổi thụng tin với nhau qua hai biến chung. Biến thứ nhất int turn xỏc định đến lượt tiến trỡnh nào được vào đoạn nguy hiểm. Biến thứ hai bao gồm hai cờ cho mỗi tiến trỡnh bool flag[2], trong đú flag[i] = true nếu tiến trỡnh thứ i yờu cầu được vào đoạn nguy hiểm.
Giải thuật Peterson được thể hiện trờn hỡnh sau:
Cú thể nhận thấy giải thuật Peterson thỏa món cỏc yờu cầu đối với giải phỏp cho đoạn nguy hiểm (yờu cầu sinh viờn thử chứng minh như bài tập nhỏ).
Việc sử dụng giải thuật Peterson trờn thực tế tương đối phức tạp. Ngoài ra nhúm giải phỏp này đũi hỏi tiến trỡnh đang yờu cầu vào đoạn nguy hiểm phải nằm trong trạng thỏi chờ đợi tớch cực (busy waiting). Chờ đợi tớch cực là tỡnh trạng chờ đợi trong đú tiến trỡnh vẫn phải sử dụng CPU để kiểm tra xem cú thể vào đoạn nguy hiểm hay chưa. Đối với giải thuật Peterson, tiến trỡnh phải lặp đi lặp lại thao tỏc kiểm tra trong vũng while trước khi vào được đoạn nguy hiểm, và do vậy gõy lóng phớ thời gian CPU.
72 Bool Test_and_Set(bool& val) {
bool temp = val; val = true; return
temp; }
const int n; //n là số lượng tiến trỡnh bool lock;
void P(int i){ //tiến trỡnh P(i)
for(;;){ //lặp vụ hạn
while(Test_and_Set(lock));//lặp đến khi điều kiện khụng thỏa <Đoạn nguy hiểm>
lock = false;
<Phần cũn lại của tiến trỡnh> }
}
void main(){
lock = false;
//tắt tiến trỡnh chớnh, chạy đồng thời n tiến trỡnh StartProcess(P(1));
...
StartProcess(P(n)); }
5.3.2 Giải phỏp phần cứng
Phần cứng mỏy tớnh cú thể được thiết kế để giải quyết vấn đề loại trừ tương hỗ và đoạn nguy hiểm. Giải phỏp phần cứng thường dễ sử dụng và cú tốc độ tốt. Dưới đõy, ta sẽ xem xột hai giải phỏp thuộc nhúm phần cứng.
5.3.2.1 Cấm cỏc ngắt
Trong trường hợp mỏy tớnh chỉ cú một CPU, tại mỗi thời điểm chỉ một tiến trỡnh được thực hiện. Tiến trỡnh đang cú CPU sẽ thực hiện cho đến khi tiến trỡnh đú gọi dịch vụ hệ điều hành hoặc bị ngắt. Như vậy, đề giải quyết vấn đề đoạn nguy hiểm ta chỉ cần cấm khụng để xẩy ra ngắt trong thời gian tiến trỡnh đang ở trong đoạn nguy hiểm để truy cập tài nguyờn. Điều này đảm bảo tiến trỡnh được thực hiện trọn vẹn đoạn nguy hiểm và khụng bị tiến trỡnh khỏc vào đoạn nguy hiểm trong thời gian đú.
Mặc dự đơn giản, việc cấm ngắt làm giảm tớnh mềm dẻo của hệ điều hành, cú thể ảnh hưởng tới khả năng đỏp ứng cỏc sự kiện cần ngắt. Ngoài ra, giải phỏp cấm ngắt khụng thể sử dụng đối với mỏy tớnh nhiều CPU. Trong khi cấm ngắt ở CPU này, tiến trỡnh vẫn cú thể được cấp CPU khỏc để vào đoạn nguy hiểm. Việc cấm ngắt đồng thời trờn tất cả CPU đũi hỏi nhiều thời gian để gửi thụng điệp tới tất cả CPU, làm chậm việc vào đoạn nguy hiểm.
5.3.2.2 Sử dụng lệnh mỏy
đặc biệt
Giải phỏp thứ hai là phần cứng được thiết kế cú thờm một số lệnh mỏy đặc biệt. Cú nhiều dạng lệnh mỏy như vậy. Ở đõy, ta sẽ xem xột một dạng lệnh tiờu biểu cú tớnh đại diện cho lệnh mỏy dựng để đồng bộ tiến trỡnh.
Nguyờn tắc chung của giải phỏp này là hai thao tỏc kiểm tra giỏ trị và thay đổi giỏ trị cho một biến (một ụ nhớ), hoặc cỏc thao tỏc so sỏnh và hoỏn đổi giỏ trị hai biến, được thực hiện trong cựng một lệnh mỏy và do vậy sẽ đảm bảo được thực hiện cựng nhau mà khụng bị xen vào giữa. Đơn vị thực hiện khụng bị xen vào giữa như vậy được gọi là thao tỏc nguyờn tử (atomic). Ta sẽ gọi lệnh như vậy là lệnh “kiểm tra và xỏc lập” Test_and_Set.
Lụgic của lệnh Test_and_Set được thể hiện trờn hỡnh sau:
Ta cú thể sử dụng lệnh Test_and_Set để giải quyết vấn đề đoạn nguy hiểm đồng thời cho n tiến trỡnh ký hiệu P(1) đến P(n) như sau:
73
Cú thể dễ dàng kiềm tra điều kiện loại trừ tương hỗ được bảo đảm khi sử dụng giải phỏp với Test_and_Set như trờn. Thật vậy, tiến trỡnh chỉ cú thể vào được đoạn giới hạn nếu lock=false. Do việc kiểm tra giỏ trị lock và thay đổi lock=true được đảm bảo thực hiện cựng nhau nờn tiến trỡnh đầu tiờn kiểm tra thấy lock=false sẽ đảm bảo thay đổi lock thành true trước khi tiến trỡnh khỏc kiểm tra được biến này. Điều này đảm bảo duy nhất một tiến trỡnh vào được đoạn nguy hiểm.
Ngoài ra, tiến trỡnh ở ngoài đoạn nguy hiểm khụng cú khả năng ảnh hưởng tới giỏ trị của lock và do vậy khụng thể ngăn cản tiến trỡnh khỏc vào đoạn nguy hiểm.
Giải phỏp sử dụng lệnh phần cứng đặc biệt cú một số ưu điểm sau: - Việc sử dụng tương đối đơn giản và trực quan.
- Giải phỏp cú thể dựng để đồng bộ nhiều tiến trỡnh, tất cả đề sử dụng chung lệnh Test_and_Set trờn một biến chung gắn với một tài nguyờn chung.
- Cú thể sử dụng cho trường hợp đa xử lý với nhiều CPU nhưng cú bộ nhớ chung. Cần lưu ý là trong trường hợp này, mặc dự hai CPU cú thể cựng thực hiện lệnh Test_and_Set nhưng do hai lệnh cựng truy cập một biến chung nờn việc thực hiện vẫn diễn ra tuần tự.
Bờn cạnh đú, giải phỏp dựng lệnh phần cứng cũng cú một số nhược điểm:
- Chờ đợi tớch cực. Tiến trỡnh muốn vào đoạn nguy hiểm phải liờn tục gọi lệnh Test_and_Set trong vũng lặp while cho tới khi nhận được kết quả lock=false.
- Việc sử dụng lệnh Test_and_Set cú thể gõy đúi. Trong trường hợp cú nhiều tiến trỡnh cựng chờ để vào đoạn giới hạn, việc lựa chọn tiến trỡnh tiếp theo khụng theo quy luật nào và cú thể làm cho một số tiến trỡnh khụng bao giờ vào được đoạn giới hạn.