region. Tương tự nếu có ít nhất một tiến trình trong vùng exit region, thì sau một thời gian sẽ có một tiến trình nào đấy đi vào vùng critical region.
Như vậy, tính chất Deadlock-freedom sẽ đảm bảo tiến trình nào cũng sẽ được thực hiện. Ở đây chúng ta chỉ coi các tiến trình là hoạt động tốt chứ chưa chú ý đến các trường hợp khác (ví dụ như các trường hợp lỗi).
Chú ý rằng sự hoạt động của hệ thống không chỉ phụ thuộc vào giao thức mà còn phụ thuộc vào NSD. Nếu một NSD giành được tài nguyên nhưng không thực hiện thao tác giải phóng tài nguyên thì hệ thống sẽ bị lỗi. Ngược lại, nếu NSD lúc nào cũng thực hiện thao tác trả lại tài nguyên thì điều kiện deadlock-freedom sẽ hiểu là hệ thống vẫn đang thực hiện (trừ khi tất cả các tiến trình đều ở trong vùng remainder).
Một điểm lưu ý là tính chất deadlock-freedom không đảm bảo các tiến trình đều có thể được vào vùng critical. Đúng hơn, nó là khái niệm chung về việc đang hoạt động (progress), tức là đang có một tiến trình nào đó cố gắng vào vùng critical. Ví dụ trong hoàn cảnh sau không vi phạm điều kiện deadlock-freedom: p1 ở trong vùng T trong khi
p2 đang chờ ở vùng R, điều kiện deadlock-freedom không đảm bảo được p1 sẽ vào được vùng C.
Có một ràng buộc khác: một tiến trình có thể có một điều khiển hoạt động cục bộ là enable chỉ khi nó ở trong T E. Điều đó có nghĩa là các tiến trình chỉ có thể thực hiện giao thức khi có yêu cầu. Nguyên nhân phải có ràng buộc này là trong mô hình chúng ta đang nghiên cứu, các tiến trình không có các bộ xử lý chuyên dụng: chúng là những tiến trình độc lập cùng thực hiện trên một bộ xử lý được chia sẻ theo thời gian. Trong mô hình chúng ta, để quản lý được các tiến trình thì các thuật toán mutual exclution phải sử dụng đến việc chuyển cảnh, ví dụ như chuyển cảnh giữa tiến trình quản lý và các tiến trình hoạt động. Trong môi trường đa bộ xử lý, ta có thể tránh được việc chuyển cảnh bằng cách sử dụng bộ xử lý chuyên biệt quản lý các tài nguyên, tuy nhiên các bộ xử này thường giám sát bộ nhớ chia sẻ, gây ra sự tương tranh về bộ nhớ. Hơn nữa, một bộ nhớ chuyên biệt để quản lý tài nguyên là không khả thi cho các công việc tính toán khác.
Các thuật toán mutual exclution
3.3.2 Thuật toán mutual exclution của Dijkstra
Thuật toán mutual exclution đầu tiên được Edsge Dijkstra phát triển vào năm 1965. Thuật toán này dự trên giải pháp two-process của Dekker[4].
Chúng ta bắt đầu với đoạn mã lệnh của thuật toán Biến chia sẻ:
flag: là một mảng số tự nhiên n phần tử [1..n] có giá trị trong tập {0,1,2}, khởi tạo các phần tử đều bằng 0. Phần tử thứ i của mảng là biến chia sẻ của tiến trình pi và được viết bởi tiến trình pi, đọc bởi tất cả các tiến trình.
turn: là một số nguyên có giá trị trong tập {1,…,n}, khởi tạo là một giá trị tuỳ ý, có thể đọc và viết bởi tất cả các tiến trình.
Đoạn mã cho tiến trình pi: **Remainder Region** tryi
**Bắt đầu giai đoạn thứ nhất, cố gắng giành được biến turn** L:flag[i] 1
while turn ≠ i do
if flag[turn] = 0 then turn i
end if end while
**bắt đầu giai đoạn 2, kiểm tra không có bộ xử lý nào khác đạt được giai đoạn này**
flag[i] 2
for i ≠ j do **Chú ý: thứ tự kiểm tra là không quan trọng** if flag[j] = 2 then goto L
end if end for criti **Critical Region** exiti flag[i] 0 remi
Chúng ta mô hình hoá đoạn mã lệnh này như sau: Mỗi trạng thái của tiến trình có các giá trị biến cục bộ và một số thông tin không có trong đoạn mã, bao gồm
Các biến tạm để lưu giữ giá trị các biến được đọc
Một bộ đếm chương trình để biết tiến trình đang thực hiện mã lệnh ở đoạn nào Các biến tạm được dùng cho luồng điều khiển chương trình (ví dụ, vòng lặp for sử dụng một tập các chỉ số để theo dõi các tiến trình)
Một định danh vùng, là T, C, E hoặc R
Trạng thái của toàn bộ hệ thống là trạng thái của tất cả các tiến trình và giá trị của tất cả các biến chia sẻ.
Trạng thái khởi tạo của hệ thống là các giá trị khởi tạo của biến cục bộ và các biến chia sẻ và các bộ đếm chương trình trong vùng remainder. (Các biến tạm có thể không định nghĩa). Các hoạt động là tryi, criti, exiti, remi, các bước tính toán cục bộ và các truy cập đọc viết tới các biến chia sẻ cục bộ. Các bước thực hiện tuân theo đoạn mã ở trên. Đoạn mã này mô tả sự thay đổi của các biến cục bộ và biến chia sẻ, tuy nhiên các biến ẩn cũng cần thay đổi theo các bước thực hiện. Ví dụ, khi xuất hiện một hoạt động
tryi, thì định danh vùng cho i sẽ là T. Và bộ đếm chương trình sẽ chuyển về phần bắt đầu của đoạn mã Trying Region.
Bổ đề 1: Thuật toán Dijkstra đảm bảo mỗi tiến trình đều phải trải qua bốn vùng của vòng tuần hoàn.
Bổ đề 2: Thuật toán Dijkstra thoả mãn tính mutual exclution. Chứng minh: Sử dụng phương pháp phản chứng
Giả sử cùng tồn tại hai tiến trình pi, pj trong vùng C, i≠j, và hai tiến trình này đều ở trạng thái hoạt động. Theo đoạn mã, trước khi vào được vùng C thì cả hai tiến trình đều gán cho biến flag giá trị là 2 tức là flag[i]=flag[j]=2. Chúng ta hãy chú ý đến đoạn mã tiếp theo, vòng lặp for thiết đặt lại giá trị các biến flag của các tiến trình khác. Không giảm tổng quát, giả sử ta có biến flag[i] được thiết đặt trước bằng 2. Sau đó, biến flag[i] sẽ giữ giá trị bằng 2 cho đến khi pi ra khỏi vùng C, và việc pi ra khỏi vùng
C phải diễn ra sau khi pj vào được vùng C vì theo điều giả định ở trên thì pi và pj cùng một lúc ở trong vùng C. Như vậy flag[i] sẽ có giá trị là 2 trong suốt thời gian pj thiết lập flag[j] bằng 2 cho đến khi pj vào được vùng C. Tuy nhiên, để vào được vùng C thì
pj lại kiểm tra được pi có giá trị khác 2(Xem hình 3.6). Như vậy xảy ra mâu thuẫn. Vậy điều giả định ở trên là sai. Vậy ta đã chứng minh xong bổ đề.
Bổ đề 3: Thuật toán Dijkstra thoả mãn tính chất deadlock-freedom.
Chứng minh: Ta lại sử dụng phương pháp phản chứng. Giả sử có thi hành hợp lệ α đạt tới điểm mà tại đó có nhất 1 tiến trình trong vùng T, không có tiến trình nào trong vùng C. Giả sử sau thời điểm này cũng không có tiến trình nào vào được vùng C. Để ý rằng tất cả các tiến trình trong T E vẫn tiếp tục tiến hành các bước trong α, và do đó, nếu có vài tiến trình trong vùng E thì bước sau đó nó sẽ ở vùng R. Vì vậy, sau một vài thời điểm trong α, tất cả các tiến trình sẽ trong T R. Hơn nữa, số lượng tiến trình là có hạn nên sau một thời gian sẽ không có tiến trình mới đi vào vùng T. Như vậy là sau một vài thời điểm trong thi hành α, tất cả các tiến trình sẽ nằm trong T R
và không có tiến trình nào thay đổi vùng nữa. Ta viết thi hành α=α1α2, trong đó trong α2 có một nhóm tiến trình vẫn mãi mãi thực hiện các bước trong T, và không xuất hiện sự thay đổi vùng. Các tiến trình này được gọi là các đối thủ.
Sau mỗi bước thi hành trong α2, mỗi đối thủ i đều đảm bảo rằng flag[i] ≥ 1, và nó duy trì giá trị này trong suốt α2.
Hình 3.6: Tại thời điểm t1, pi thiết đặt flag[i]=2; tại thời điểm t2, pj lại thấy flag[i] ≠ 2; tại thời điểm t3 thì pi rời khỏi vùng C
Chúng ta có khẳng định sau: trong α2, cuối cùng thì biến turn cũng giành được một giá trị chỉ số của đối thủ.
Ta sẽ chứng minh khẳng định này. Giả sử giá trị của turn không là chỉ số của bất kỳ đối thủ nào trong suốt α2. Sau đó, khi một đối thủ pi kiểm tra, nó sẽ thấy turn ≠ i và
flag[turn]=0 và nó sẽ thiết đặt turn=i. Chắc chắn sẽ tồn tại một tiến trình i như vây, vì tất cả các đối thủ đều hoặc là nằm trong vòng lặp while hoặc nếu nằm trong giai đoạn 2 thì khi gặp thất bại lại quay về vòng lặp while(do theo giả định, không có tiến trình nào nằm trong C trong suốt α2). Như vậy trong α2, turn sẽ giành được một chỉ số của các đối thủ.
Khi turn giành được chỉ số của các đối thủ, nó sẽ duy trì giá trị theo chỉ số mặc dù giá trị đó có thể thay đổi theo các đối thủ khác nhau. Sau đó khi đọc biến turn và
flag[turn] thì thấy flag[turn]≥1 (Vì tất cả các đối thủ i đều có flag[i]≥1), giá trị của
turn sẽ không thay đổi nữa, giá trị turn sẽ ổn định cho đến chỉ số cuối cùng. Lấy α3 là bước thi hành tiếp ngay sau α2 mà tại đó giá trị của turn đã ổn định, ví dụ bằng i.
Lúc này trong α3, tất cả các đối thủ j≠i đều bị lặp vĩnh viễn trong vòng lặp thứ nhất. Nếu tiến trình nào ở trong giai đoạn 2 thì sau khi không vào được vùng C nó sẽ quay lại nhãn L. Và nó sẽ bị tắc ở giai đoạn 1, bởi vì turn=i≠j và flag[i]≠0. Đặt α4 là thi hành tiếp ngay sau α3 mà tại đó tất cả các đối thủ khác i bị lặp mãi mãi trong vòng lặp thứ nhất. Trong suốt α4 tất cả các đối thủ khác i đều có giá trị của biến flag bằng 1.
Khi vòng lặp kết thúc, tiến trình i sẽ có flag[i]=2 và sẽ không có tiến trình nào có
flag=2, do đó tiến trình i sẽ qua được giai đoạn 2 và vào được vùng C. Như vậy là trái với giả thiết. Vậy điều giả định là sai(đpcm).
Thời gian thi hành: Thời gian được tính toán kể từ lúc có vài tiến trình trong vùng T và không có tiến trình nào trong vùng C cho đến khi có tiến trình vào được vùng C. Chúng ta coi như thuật toán bắt đầu tại thời điểm 0. Thời gian lớn nhất cho mỗi bước của các tiến trình là s, thời gian lớn nhất ở trong vùng C là c. Người ta đã chứng minh được rằng: tại một thời điểm cụ thể trong đó có tiến trình nào đó nằm trong T và không có tiến trình nào nằm trong C thì trong khoảng thời gian O(s.n), sẽ có tiến trình được vào vùng C.
Kết luận: Thuật toán Dijkstra đã giải quyết được vấn đề Mutual exclution và deadlock-freedom. Tuy nhiên còn một số vấn đề mà thuật toán vấn không giải quyết được. Đó là trường hợp có một tiến trình a sẽ được cấp quyền truy cập vào vùng C thì một tiến trình b khác cũng đang giành quyền truy cập và ngăn cản tiến trình a, khi đó tiến trình sẽ bị đói tài nguyên. Một hạn chế khác là thuật toán Dijkstra sử dụng biến turn có tính chất multi-reader/multi-writer. Khi triển khai những hệ thống như vậy rất khó khăn và chi phí cao. Thuật toán được trình bày sau đây của Peterson sẽ khắc phục được các hạn chế này.
3.3.3 Thuật toán Two-process của Peterson
Chúng ta bắt đầu giải pháp 2 tiến trình cho tiến trình p0 và p1. Đặt i= i-1 là chỉ số của các tiến trình còn lại[4]. Đoạn mã như sau:
Biến chia sẻ:
level: là một mảng Boolean có giá trị là 0 hoặc 1, khởi tạo bằng 0
turn: là biến kiểu Boolean, khởi tạo là trạng thái tuỳ ý. Đoạn mã cho tiến trình pi
**Remainder Region** tryi
level[i]:=1 turn:=i
wait for level(i)=0 or turn ≠ i criti
**Critical Region** exiti
level[i]:=0 remi
Định lý 1: Thuật toán two-process của Peterson thoả mãn tính mutual exclution Chứng minh: trước tiên chúng ta có định nghĩa cho các tập như sau:
before-C: là tập các tiến trình có bộ đếm chương trình ở ngay sau vòng lặp cuối cùng
in-C: là tập các tiến trình có bộ đếm chương trình ở trong vùng C Ta có:
level(i)=0 => i (atwaitbeforeCinC)(*) Từ (*) ta có thể suy ra: ) ( )) ( ( )
(before C in C i at wait before C in C turn i
i
Có 3 bước kiểm tra:
- Thứ nhất là i qua được giai đoạn wait. Khi đó ta sẽ có hoặc là turn≠i và i vào được vùng C, hoặc là level(i)=0, đó là trường hợp (*).
- Thứ hai khi i đạt tới trạng thái at-wait thì nó sẽ thiết đặt turn≠i
- Thứ ba khi turn được gán cho giá trị i thì đó là một bước của tiến trình pi được thực hiện khi nó không ở trong vùng được chỉ ra.
Định lý 2: Thuật toán two-process của Peterson thoả mãn tính deadlock-freedom Chứng minh: Chúng ta sẽ dùng phương pháp phản chứng.
Giả sử tại một thời điểm, i T, không có tiến trình nào trong C và sau đó cũng không có tiến trình nào đi vào C. Có hai trường hợp xảy ra.
Nếu cuối cùng i1 vào T thì cả hai tiến trình sẽ sa lầy vào câu lệnh wait, từ đó chúng không vào được C, nhưng điều này không thể xảy ra. Từ đó giá trị của biến turn phải là chỉ số của một trong hai tiến trình đó.
Tình huống thứ 2 : Giả sử rằng i1 không bao giờ vào T. Trong tình huống này bằng phương pháp qui nạp, cuối cùng ta cũng chỉ ra rằng level(i1) = 0. Trái với giả sử rằng i vẫn đang bị sa lầy trong câu lệnh wait của nó.
Định lý 3:
Thuật toán hai tiến trình của Peterson đảm bảo thuộc tính lockout-freedom Phác hoạ chứng minh
Dùng chứng minh phản chứng: giả sử rằng
i đang ở trong T sau đó thiết lập level(i) = 1 và i1 vào C ba lần.
Qua đoạn mã ta thấy , trong lần vào C thứ hai và thứ ba thì tiến trình i1 sẽ thiết lập turn := i1 và sau đấy ta lại thấy turn = i. Có nghĩa là có hai lần i phải thiết lập turn := i
Nhưng theo giả thuyết thì turn := i chỉ được thực hiện một lần trong suốt thời gian tiến trình i ở trong vùng T.
Trái với giả thiết. => đpcm.
Vậy qua hai thuật toán mà khoá luân đã đề cập ở trên, cả hai thuật toán đều giải quyết được vấn đề tương tranh cho hệ thống, trong đó thuật toán Two-process được hỗ trợ thuộc tính lockout-freedom, đảm bảo các tiến trình tới đều được sử dụng tài nguyên. Nhưng cả hai thuật toán đó đều gặp phải một khó khăn chung đó là khi triển khai thuật toán đều sử dụng các biến kiểu multi-writer (turn). Do đó trên thực tế để triển khai các biến chia sẻ kiểu multi-write trên các hệ thống phân tán là rất khó khăn và hiệu quả thấp đó cũng là một những nguyên nhân làm cho các thuật toán này rời xa tính thực tiễn. Trong phần tới đây của khoá lụân có đề cập tới hai thuật toán khác đó là mutual exclusion của Burn và Bakery của Lamport, cả hai thuật toán này đều không sử dụng các biến kiểu multi-writer để triển khai thuật toán nữa thay vào đó sử dụng các biến chia sẻ kiểu single-writer.
3.3.4 Thuật toán mutual exclusion của Burn
Cả hai thuật toán mà chúng ta đã nghiên cứu từ trước cho tới nay đó là Dijkstra và Peterson, thì đều sử dụng các biến multi-writer (turn) cùng với một tập các biến single- writer ( flag ). Như ta đã đề cập tới ở trên việc triển khai thuật toán đó vào ứng dụng rất khó, chi phí cao và thi hành không hiệu quả các biến chia sẻ multi-writer trong các hệ thống hiện thời (cụ thể là các hệ thống phân tán), các thuật toán mà chỉ sử dụng các biến chia sẻ single-writer thì được triển khai hiệu quả hơn và giành sự quan tâm đặc biệt của các nhà khoa học cũng như các nhà triển khai hệ thống. Chúng ta sẽ tìm hiểu hai thuật toán mà chỉ sử dụng biến chia sẻ single-writer của Lamport và Burns[4].
Thuật toán thứ nhất được phát triển bởi Jim Burns.Trước tiên, để hiểu được tư tưởng cũng như cách triển khai thuật toán để giải quyết vấn đề tương tranh, Burns có