• 沒有找到結果。

The above algorithm is 2-bounded-bypass. This section gives a FCFS algorithm, based on the 2-bounded-bypass algorithm, with the same number of shared variables and the same set of primitives.

The FCFS algorithm follows the same concept of the 2-bounded-bypass algo-rithm, except that it initiates an additional phase to redirect the links in a list to meet the FCFS condition. Owing to the implementation of the communication

phase, the number of values taken on by the shared variable P increases from (n+1)2 to 2(n + 1)3.

4.2.1 An Informal Description of the Algorithm

The FCFS algorithm also organizes waiting processes into circular lists. Each process in its doorway announces its request by executing f etch&store on the shared variable L. In this step, a contending process obtains the identity of its predecessor if it has one, and replaces L with its identity. As in the 2-bounded-bypass algorithm, a process gaining nil from L is the head of the list that it closes, and takes the role of a controller. That is, the process closes the list, and starts to transmit the permission along the list, when it leaves the critical region.

However, the FCFS algorithm conveys the permission along a list in the reverse order. Recall that a waiting list in the 2-bounded-bypass algorithm is ordered from the tail to the head, causing the algorithm to fail the FCFS condition. Each process in a waiting list, except the head, has the identity of its predecessor rather than its successor. To achieve the FCFS requirement, an additional communication phase is required to inform each process of its successor’s identity, so that the permission can be passed in the FCFS order.

The algorithm initiates such a phase by the controller of a list when the controller leaves C. Starting from the tail, each non-controller except the immediate successor of the head writes a message in turn to inform its predecessor of its identity. The communication phase is completed when the immediate successor of the head re-ceives its successor’s identity. The permission is then conveyed from the successor of the head to the tail. The algorithm thus satisfies the FCFS condition.

Implementing this phase requires some communication mechanism. In the algo-rithm, the shared variable P is used for two purposes: to indicate which process is permitted to enter C, and to inform processes of their respective successors. The use of a shared variable for these two purposes is inspired by the algorithms [10]

proposed by Burns et al. To serve both purposes, the variable holds a 4-tuple

Figure 4.3: An execution of the FCFS algorithm. The notation is the same as that in Fig. 4.1.

(Type, Receiver , Successor , Head ), where Type is a value in {Info, Grant}, and the other parts take on values from {nil, 1, . . . , n}. The number of values taken on by P in this algorithm is 2(n + 1)3, compared with (n + 1)2 in the 2-bounded-bypass algorithm.

The Type component represents the purpose of a variable. If Type has the value Grant, then variable P is adopted to convey the permission. In this case, the Receiver component represents the process that has the permission, while the Successor and Head are not used, and may have arbitrary values, denoted as 4 in the algorithm. Otherwise, if the T ype component has the value Info, then variable P is used to inform some process of its successor. In this case, Receiver represents the receiver of the message; Successor represents the identity of the receiver’s successor, and Head represents the identity of the head of the list.

Figure 4.3 illustrates an execution of the algorithm. The sequence of requests in list 1 is the same as that given to the 2-bounded-bypass algorithm in Fig. 4.1, but the order in which the permission is conveyed among non-controllers is opposite.

Variables L and P are initially set to nil and (Grant, nil, 4, 4), respectively.

In Fig. 4.3(a), processes 5, 2, 6, 4 make requests in turn and constitute a list.

Because process 5 obtains nil from L, and receives a message that Type = Grant and Receiver = nil, it has the permission for a controller. It enters C after setting P to (Grant, 5, 4, 4). In contrast, processes 2, 6, and 4 repeatedly test P until they receive their respective messages. Process 5 performs fetch&store(L, nil) to obtain the identity of the tail, 4 in this case, and modifies L’s value to nil, when it leaves C. It then starts a communication phase by writing (Info, 4, nil, 5), as shown in Fig. 4.3 (b). This message notifies process 4 that it is the tail of the list (because Successor = nil), and that the head is process 5. Process 4 then receives the message and writes a new message (Info, 6, 4, 5) to process 6, as shown in Fig. 4.3(c). The new message informs process 6 that its successor is process 4, and that the head of the list is process 5. Similarly, process 6 receives the message from process 4, and writes a new message (Info, 2, 6, 5) to inform process 2, as shown in Fig. 4.3(d).

Process 2 receives the message written by process 6, and becomes aware that it is the immediate successor of the head, because its predecessor is the head of the list. This means that the communication phase is completed. Process 2 enters C, and conveys the permission to its successor, process 6, by writing (Grant, 6, 4, 4) into P , as shown in Fig. 4.3(e). Process 6 then gains the permission, and conveys it to process 4, as shown in Fig. 4.3(f). Since process 4 is the tail of the list, process 4 hands the permission to the next waiting list, by setting P to (Grant, nil, 4, 4), when it leaves C, as shown in Fig. 4.3(g). Process 1, which is the head of the next list, then receives the permission to enter C, as shown in Fig. 4.3(h), because Type = Grant and Successor = nil.

Shared variables:

pred, tail, receiver, successor, head∈ {nil, 1, . . . , n}, initially arbitrary

while true do

R: Remainder region

T1: pred:= fetch&store(L, i);

T2: if pred= nil then as a controller

T3: (type, receiver, successor, head) := P ; T4: while type6= Grant or receiver 6= nil do T5: (type, receiver, successor, head) := P od T6: P := (Grant, i, 4, 4);

else as a non-controller

T7: (type, receiver, successor, head) := P ; T8: while receiver6= i do

T9: (type, receiver, successor, head) := P od T10: if type= Info and pred 6= head then

T11: P := (Info, pred, i, head);  inform its predecessor

T12: gotoT7;

fi fi

C: Critical region

E1: if pred= nil then as a controller

E2: tail:= fetch&store(L, nil); close the waiting list E3: if tail6= i then

E8: P := (Grant, successor, 4, 4) fi wake up the successor fi

od

Figure 4.4: The FCFS algorithm.

4.2.2 Proposed Algorithm

This subsection describes the algorithm in more detail. Each process stores the values of the four components of shared variable P into its private variables type, receiver, successor and head .

In the trying region, the doorway is composed of line T1 in Fig. 4.4, and the waiting part is composed of the rest (T2–T12). If the returned value of T1 is nil (i.e., pred = nil), then process i identifies itself as a controller, and repeatedly checks P until it gains the permission for a controller (i.e., type = Grant and receiver = nil) (T4–T5). When type = Grant and receiver = nil, process i enters C after setting Receiver to i (T6). In contrast, if pred 6= nil, then process i repeatedly tests P until a message belonging to it is received (T8–T9). If the received message has value Grant in the Type component, then i enters C immediately; otherwise, if the message has value Info in Type, then two cases may occur.

1. When pred = head , process i is the immediate successor of the head. In other words, the communication phase is completed and i is permitted to enter C.

2. When pred 6= head , process i conveys its own identity and the identity of the head to its predecessor (T11), and continues to check P (T12).

In the exit region, a controller i performs fetch&store(L, nil) (E2) to close the current waiting list, and stores the returned value in tail . If tail is not equal to its identity, then the list contains some process other than the controller, and there-fore the controller starts a communication phase by writing (Info, tail, nil, i) into P (E4). This value indicates that the receiver is the tail of the list; that the tail has no successor because the Successor part is equal to nil, and that the head of the list is process i. Otherwise, if tail equals the controller’s identity, then it just modifies P ’s value to the initial value, (Grant, nil, 4, 4) (E5). For every non-controller, if successor = nil holds, then it realizes that it is the tail of the list, and gives the permission to the next list by writing (Grant, nil, 4, 4) into P (E7). Otherwise, it hands the permission to its successor by changing P ’s value to (Grant, successor, 4, 4) (E8).

4.2.3 An Informal Correctness Argument

Since the correctness argument of the 2-bounded-bypass algorithm has shown the basic idea about arranging waiting processes into lists, this subsection simply pro-vides a proof sketch. The notation and the definition of a controller are the same as those in Section 4.1.3.

The mutual exclusion condition is proven by the strategy adopted in the 2-bounded-bypass algorithm. First, a process in E enables at most one process to enter C, and at most one process can enter C from the starting state.

In the algorithm, a process i in T is permitted to enter C only if one of the following conditions holds.

Condition 1: type = Grant and receiver = nil hold. Informally, process i, which is a controller, obtains the permission to enter C.

Condition 2: type = Grant and receiver = i hold. Informally, process i, which is a non-controller, obtains the permission to enter C.

Condition 3: type = Info, receiver = i and pred = head hold. Informally, process i, which is aware that it is the immediate successor of the controller and that the communication phase is finished, obtains the permission to enter C.

Inspecting the algorithm indicates that a process in E performs exactly one of E4, E5, E7 and E8 to change P ’s value. As shown below, each step enables at most one process to enter C.

Step E4 modifies P ’s value to (Info, tail, nil, i). A value in P is said to be a communication word if Tpye = Info. According to the algorithm (T10–T12), a communication word may enable the receiver of the word to satisfy Condition 3. If not, the receiver writes a new communication word to its predecessor and backs to step T7. Thus, step E4 enables at most one process to satisfy Condition 3.

Step E8 modifies P ’s value to (Grant, successor, 4, 4), making the process whose identity is equal to successor to satisfy Condition 2.

Steps E5 and E7 set P to the initial value, (Grant, nil, 4, 4). Using the argu-ment in Lemma 4.1, we have the counterpart of Lemma 4.1 below.

Lemma 4.5 At any reachable system state,

| { i ∈ P | predi = nil ∧ pci ∈ {T3, T4, T5, T6}} | ≤ 1.

Namely, at most one controller is blocked because of Condition 1 at any reachable state, so that the initial value allows at most one process to enter C. This implies that E5 and E7 each enable at most one process to gain the permission, and furthermore implies that at most one process can enter C from the starting state, at which P has the initial value. Thus, the mutual exclusion condition is ensured.

We now argue that the proposed algorithm satisfies the lockout-freedom condi-tion. The argument is similar to that of Theorem 4.3. A process i is said to be in the doorway if pci = T1, and it is said to be in the waiting part if pci ∈ {T2, . . . , T12}.

Due to the f etch&store primitive, each requesting process is properly arranged in a list after finishing its doorway. Moreover, each process has the identity of its predecessor, by which the head of a list initiates a communication phase to reverse the order in a list. Consequently, the permission can be conveyed according to the sequence of the requests; the algorithm thus satisfies not only the lockout-freedom condition, but also the FCFS condition.