• Nenhum resultado encontrado

The Concurrent Programming Language CLASS

4.3 Dining Philosophers

Crucially, for this implementation to work, methodmdeq(𝑚)(Fig.4.4) for dequeueing needs to be adapted. On each dequeue usage, the head element of the linked list to which the dequeue usage points is copied. One of the copies is sent to the invoking client of the methodmdeq(𝑚), after which the dequeueing pointer moves by one position. The other copy is used to restore the head element to the linked list, so that it is still available to possible independent duplicated dequeue usages.

Since we need to copy elements of the linked list, this implementation only works with linked listsS𝑓 LL(!𝐴)that store exponentials !𝐴. The dequeue operation can then be straightforwardly implemented with the exponential read operation, which was intro-duced previously in Example13.

On the other hand, method

mdup(𝑚) ⊢ 𝑚:𝑋 ⊸(𝑋O𝑋

O⊥), where𝑋 =S𝑓 LL(!𝐴) that duplicates a pointer𝑚is defined by

mdup(𝑚) ≜ recv𝑚(𝑐);recv𝑚(𝑐1);recv𝑚(𝑐2);take𝑐(𝑙);use𝑙;wait𝑚; share𝑙{

put𝑐(𝑙.affine𝑙;fwd𝑙 𝑙);fwd𝑐 𝑐1

| |

cell𝑐2(𝑙.affine𝑙;fwd𝑙𝑙)}

It inputs on𝑚an usage𝑐for dequeueing and produces two duplicated usages𝑐1, 𝑐2. The linked list𝑙stored in𝑐is shared between two concurrent threads: one puts back the list𝑙 on𝑐and forwards𝑐1to𝑐, whereas the other creates on𝑐2a new cell that stores𝑙.

Code for the imperative queue (server side and client) as well as collection of tests can be found instate/imperative-queue. The variation in which exponential cells are stored and which allows the head pointer to be duplicated can be found instate/imperative-queue-B.

𝑓𝑖, 𝑓𝑖+1; philosopher𝑃𝑘−1must acquire forks 𝑓0and 𝑓𝑘−1. After eating, each philosophers drops the forks and starts to think. No pair of two philosophers can communicate, the problem is then to design a decentralised fork-acquiring policy so as to avoid deadlock.

Dijkstra’s resource hierarchy solution works by requiring each fork to be acquired by ascending order, i.e. if a philosophers acquires first 𝑓𝑖 and and then 𝑓𝑗 we have𝑖 < 𝑗. In other words, each philosopher𝑃𝑖 with 0 ≤ 𝑖 < 𝑘−1 acquires first its right and left fork, whereas philosophers𝑃𝑘−1 is the symmetry-breaker that must acquire first its left and only then its right fork.

In order to code this solution inCLASS, the key idea is to represent the order in which the forks must be acquired by an explicit acyclic linked chain. More specifically, let us define the following recursive datatype structure

Fork ≜S𝑓 Node Node∧ ⊕ {#Null:1,#Next:Fork}

A process offering the recursive typeForkbehaves as a cell which stores a node of session Node. A session of typeNodeeither chooses #Null, in which case it closes; or chooses #Next, in which case it continues as the nextFork. Define the basic operationsnull(𝑛) ⊢𝑛:∧Node andnext(𝑓 , 𝑛) ⊢ 𝑓 :Fork, 𝑛:∧Nodeby

null(𝑛)≜affine𝑛; #Null𝑛;close𝑛 next(𝑓 , 𝑛)≜affine𝑛; #Next𝑛;fwd𝑛 𝑓

A resource hierarchy on𝑘forks 𝑓0 < 𝑓1<. . .< 𝑓𝑘is then represented inCLASSby the passive linked chain

cut{cell 𝑓0(𝑛0.next(𝑓1, 𝑛0))|𝑓1| cell 𝑓1(𝑛1.next(𝑓2, 𝑛1))|𝑓2| . . . |𝑓𝑘|cell 𝑓𝑘(𝑛𝑘.null(𝑛𝑘))}

If a philosopher is granted access to a fork 𝑓𝑖, he can then access any fork 𝑓𝑗, with𝑖 < 𝑗, by traversing the pointed structure. Crucially, he must follow the path dictated by the chain and hence, cannot acquire forks 𝑓𝑗 with𝑗 <𝑖.

The chain on forks can then be shared by an arbitrary number of dining philosophers by applying cocontraction. Cocontraction ensures that philosophers do not communicate between themselves, any synchronisation happens through the shared passive ordered structure on forks.

In Fig.4.5, on the left, we show how an example with a round table of four philosophers more specifically with two logicians - William Howard (𝑃0) and Jean-Yves Girard (𝑃3) -and two computer scientists - Robin Milner (𝑃1) and Kohei Honda (𝑃2), and the four forks numbered from 0 to 3. The order on the forks is captured inCLASSby an acyclic linked chain, represented on the right. A fork is represented with a blue box, the dashed black box represents the end of the chain. Philosophers𝑃0, 𝑃1, 𝑃2, 𝑃3have access to a particular fork of the chain as represented by the dashed links. Each philosophers𝑃0, 𝑃1, 𝑃2eats by acquiring consecutive forks in the chain. The symmetry-breaker philosopher𝑃3must eat by acquiring first the head fork 𝑓0and then needs to traverse the whole structure in order to acquire the last fork 𝑓3.

Figure 4.5: Solution to the dining philosophers problem inCLASS.

We will now describe in detail processes that implement eating for a system with𝑘 philosophers. Each philosopher𝑃𝑖, with 0≤ 𝑖< 𝑘−1 eats by acquiring two consecutive forks, whereas philosopher𝑃𝑘−1eats by acquiring the first 𝑓0 and last fork 𝑓𝑘−1. Eating for a philosopher 𝑃𝑖, with 0 ≤ 𝑖 < 𝑘 −1 is implemented by process eat, whereas eat2 implements eating for philosophers𝑃𝑘 (Fig.4.6).

Process eat(𝑓 , 𝑓) ⊢ 𝑓 : Fork, 𝑓 : Fork takes fork 𝑓 on parameter𝑛 (ln. 2), then it performs case analysis to check if𝑛 is #Nullor #Next(ln. 4). If #Nullit simply puts null back on 𝑓 and forwards to 𝑓, this is done by auxiliary processputNull(𝑓 , 𝑓)(ln. 6).

The interesting case occurs when𝑛selects #Next, in which case a further take occurs (ln. 7). The philosophers has now acquired the two forks - 𝑓 and𝑛 - and can eat, we left code for eating unspecified. Then, the philosopher puts back the two forks (ln. 9-10), while preserving the original chain structure, and forwards fork 𝑓 to 𝑓(ln. 11) which can then be used by subsequent rounds of eating.

In a system with 𝑛 philosophers, philosopher 𝑃𝑛(𝑓0) has access to the head of the chain but eats in a distinct way, as described by processeat2(𝑓 , 𝑓) ⊢ 𝑓 :Fork, 𝑓 :Forkin Fig.4.6. As it happened before, it starts by taking the first fork which contains always a nonempty node, but then it has to take the last fork in the chain, which is done with the auxiliary process takeLast(𝑛, 𝑥) ⊢ 𝑛 : Fork, 𝑥 : Fork⊗1, which recursively traverses the whole chain𝑛until it takes the last fork, after which it eats, puts back the last fork, sends the unmodified structure𝑛on𝑥and closes.

Inevitably, the way in which philosopher𝑃𝑘 eats has to be distinct so as to ensure the key symmetry-breaking, implicit in Dijkstra’s original resource hierarchy solution:

philosophers 𝑃0, . . . , 𝑃𝑘−1 all pick the first the left and then the right forks, whereas philosopher𝑃𝑘 starts by picking the right forks and then picks the left.

Interestingly, further partial orders on resources, other that simple chains, can also be explored, for example: we can model directed trees simply by redefiningNodeas a linked list of forks.

Code for this example can be found instate/dining-philosophers.clls.

1 : eat(𝑓 , 𝑓)≜ 2 : take 𝑓(𝑛); 3 : use𝑛; 4 : case𝑛{

5 : |#Null: wait𝑛; 6 : putNull(𝑓 , 𝑓) 7 : |#Next: take𝑛(𝑚); 8 : . . .//eating

9 : put𝑛(𝑚.fwd𝑚 𝑚); 10 : put 𝑓(𝑛.next(𝑛, 𝑛));

11 : fwd 𝑓 𝑓}

1 : eat2(𝑓 , 𝑓)≜ 2 : take 𝑓(𝑛); 3 : use𝑛; 4 : case𝑛{

5 : |#Null: wait𝑛; 6 : putNull(𝑓 , 𝑓)

7 : |#Next: cut{takeLast(n,x) |𝑥|

8 : recv𝑥(𝑚);

9 : wait𝑥;

10 : put 𝑓(𝑛.next(𝑚, 𝑛));

11 : fwd 𝑓 𝑓}}

1 : putNull(𝑓 , 𝑓)≜ 2 : put 𝑓(𝑛.null(𝑛)); 3 : fwd 𝑓 𝑓

1 : takeLast(𝑓 , 𝑥)≜ 2 : take 𝑓(𝑛); 3 : use𝑛; 4 : case𝑛{

5 : |#Null: wait𝑛; 6 : . . .//eating

7 : put 𝑓(𝑛.null(𝑛)); 8 : send𝑥(𝑓);

9 : close𝑥

10 : |#Next: cut{takeLast(𝑛, 𝑦) |𝑦|

11 : recv𝑦(𝑚);

12 : put 𝑓(𝑛.next(𝑚, 𝑛));

13 : wait𝑦;

14 : send𝑥(𝑓);

15 : close𝑥}}

Figure 4.6: Dining philosophers: implementation details.