Paralell Programming Exam
Paralell Programming Exam
U NIVERSITET
Alejandro Russo, Computer Science and Engineering
c) Extend your solution from the previous point to simulate what happens at San Marcuolaen.
For that, you should consider that every passenger must get off before the traghetto turns
back for more passengers. Similarly than for boarding, passengers can get off the traghetto
at the same time. It is important that if a passenger is too slow to get off the traghetto, the
other passengers do not necessarily need to wait to abandon the gondola.
(5p)
1. import edu.ucdavis.jr.JR;
public class TraghettoFull {
private int total_crossing = 9;
private
private
private
private
sem
sem
sem
sem
wait_to_board = 0 ;
boarding_done = 0 ;
wait_to_getoff = 0 ;
got_off
= 0 ;
process Traghetto {
int crossed = 0;
while(crossed < total_crossing){
System.out.println("At San Marcoulaen - Crossing");
JR.nap(1000) ;
System.out.println("Arrived to Fondaco dei Turchi") ;
for (int i=0; i < 3; i++)
V(wait_to_board) ;
for (int i=0; i < 3; i++)
P(boarding_done) ;
JR.nap(300) ;
System.out.println("Arriving to San Marcoulaen with passengers!");
System.out.println("Start getting off!");
for (int i=0; i < 3; i++)
V(wait_to_getoff) ;
for (int i=0; i < 3; i++)
P(got_off) ;
crossed = crossed + 3 ;
System.out.println("Done with this round!");
}
}
process Passenger((int i=0;i<total_crossing;i++)) {
System.out.println("Passenger "+i+" waiting for the Traghetto!");
P(wait_to_board) ;
System.out.println("Boarding "+i);
V(boarding_done) ;
P(wait_to_getoff) ;
System.out.println("Descending "+i);
V(got_off) ;
System.out.println("Goodbye "+i);
//
}
public static void main(String[] args) {
new TraghettoFull();
}
To get full points your solution must fulfill the following criteria:
You must use semaphores for synchronization or mutual exclusion. No other synchronization constructs are allowed.
You can use either Java or JR. If you choose to use JR, remember that the primitive sem is
for declaring semaphores (e.g. sem s), the primitive P(s) for acquiring semaphore s, and
V(s) for signaling semaphore s.
Passengers must all execute the same code.
Question 2. For this exercise, you should implement a server that stores and handles an unbounded buffer in
JR. For simplicity, we assume that the buffer consists on integer numbers. Your implementation
must serve the operations put buffer and get buffer related to putting and getting numbers
from the buffer, respectively. As usual, if any of these operations cannot be completed because
some condition was not fulfilled (e.g., the buffer is empty), then the thread should block until
that condition is satisfied. Below, you can see an skeleton of a server implementation serving
the mentioned operations.
op void put_buffer(int);
op int get_buffer();
op void buffer(int);
op int pending();
while (true)
{
inni void put_buffer(int x) st pending.length() == 0 {
...
}
[]
The operation buffer represents the buffer of integer numbers. The operation pending is a
queue of threads waiting for the buffer to get populated in order to fetch a number.
Your assignment Your task is to fill in the dots in the skeleton provided above in order to
complete the implementation of the server.
To get full points your solution must fulfill the following criteria:
You must use forward.
You must use message passing for synchronization. No other synchronization constructs
are allowed.
(10p)
2. public class UnBuff {
public op void put_buffer(int);
public op int get_buffer();
Question 3. Background
Function visitURL receives as an argument an URL and returns true when the webpage has
been visited previously. Function addURL is invoked everytime that a web site needs to be added
to the cache. Observe that while visitURL reads from the cache, addURL writes into it.
Your Assignment Your task is to implement a new class ThreadSafeCache which utilizes the
old class but that can be safely used by different threads at the same time. Your class should
have the same interface as the old one. It is important to guarantee that several threads can
obtain information about the presence of an URL in the cache (i.e., by calling visitURL) at
8
the same time. There must be no more than one instance of addURL running at a given time.
Please, do not care about fairness in your solution.
As company policy at Morzilla, all programmers must use Java 5 monitors as synchronization
primitive. Here is a short reference of what you will need from java.util.concurrent.locks.
class ReentrantLock {
public ReentrantLock();
public Condition newCondition();
public void lock();
public void unlock();
}
class Condition {
public void await();
public void signal();
public void signalAll();
}
(8p)
import java.util.concurrent.*;
class ThreadSafePlayList {
private PlayList playlist;
private final Lock lock = new ReentrantLock();
private final Condition waitingToRead = lock.newCondition();
private final Condition waitingToWrite = lock.newCondition();
private int readers = 0;
private int writers = 0;
public ThreadSafeCache() {
cache = new Cache();
}
public int visitURL(URL url) {
int result ;
lock.lock();
while (writers > 0)
waitingToRead.await();
readers++;
lock.unlock();
result = cache.visitURL(url);
lock.lock();
readers--;
if (readers == 0)
waitingToWrite.signal();
lock.unlock();
return result;
}
public void addURL(URL url) {
lock.lock();
while (readers > 0 || writers > 0)
waitingToWrite.await();
writers++;
lock.unlock();
cache.addURL(url);
lock.lock();
writers--;
waitingToWrite.signal();
waitingToRead.signalAll();
lock.unlock();
}
}
Question 5. Background
We introduce here a simplified version of a common concurrent pattern used in practice. The
task is to distribute work tasks among a number of workers. A worker is a process that can
perform a computation. Workers can be active or passive. An active worker is a process already
performing computations related to a task(s), while passive is a process waiting to be assigned
to a task(s). We have a server that keeps track of the tasks to be performed and has a fix number
of workers willing to take those tasks. More specifically,
The initial state of the server is a queue of tasks and a list of passive workers.
A worker can take more than a single task.
An active worker becomes passive after finishing with the assigned task(s). Similarly, a
passive worker becomes active when being assigned a task(s).
The server finishes execution when the queue of tasks is empty and there are no active
workers.
The server waits for a worker to return a result when the queue is empty or there are no
more passive workers.
When the task queue is not empty, the server gets a passive worker and assign some chunk
of tasks to perform, i.e., the worker is now active.
How to get a chunk of work from the queue and how to combine results from a single worker
with the result from the others is something specific for each problem. However, in order to not
make you worry about that, we assume that there exists the following functions.
combine_result(Result, Results)
obtain_tasks(Tasks)
Function combine_result takes a newly produce result and a list of previously produced results
and returns a list with the combined results. Function obtain_tasks takes a list of tasks Tasks
and returns a tuple. The first component of the tuple is a chunk of tasks, written Chunk, and the
second component is the list Tasks without the tasks described in Chunk.
Your assignment Your task is to provide an implementation of the server that distributes tasks
among workers in Erlang. For instance, if the server is implemented as the function work_load,
then the initial call to that function must be work_load(Tasks, Passive, ...), where Tasks is
the list of tasks to be computed and Passive is a list of PID of the passive workers. We use
... to denote that there are more arguments in the server that you should think about. The
result of calling work_load must be the result produced when the server finishes execution.
Every worker executes the following code, which might give you a hint about how the code for
work_load looks like.
10
worker() ->
receive {Pid, Tasks} ->
Result = compute(Tasks),
Pid ! {self(), Result},
worker()
end.
Function compute just computes a result with the given tasks. Even though it sounds difficult,
do not be afraid, the solution of this exercise is just a few lines in Erlang!
(10p)
-module(server_workers).
-export([work_load/4, worker/0]).
work_load([], _, [], Results) -> Results;
work_load([Task | Tasks], [Worker | Passive], Active, Results) ->
{Chunk, TTasks} = obtain_tasks([Task | Tasks]),
Worker ! {self(), Chunk},
work_load(TTasks, Passive, [Worker | Active], Results).
work_load(Tasks, Passive, Active, Results) ->
receive {Worker, Result} ->
work_load (Tasks,
[Worker | Passive],
lists:delete(Worker, Active),
combine_result(Result, Results))
end;
11