0% found this document useful (0 votes)
40 views10 pages

Synchronization Tools: Exercises

The document discusses various synchronization tools and exercises related to race conditions, mutex locks, and semaphore implementations in concurrent programming. It includes pseudocode examples and questions that explore potential race conditions and their solutions in different scenarios, such as online auctions and multi-core systems. Additionally, it addresses the implications of using compare-and-swap instructions and the design of monitors for resource allocation and access control.

Uploaded by

Paradise TheKing
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
40 views10 pages

Synchronization Tools: Exercises

The document discusses various synchronization tools and exercises related to race conditions, mutex locks, and semaphore implementations in concurrent programming. It includes pseudocode examples and questions that explore potential race conditions and their solutions in different scenarios, such as online auctions and multi-core systems. Additionally, it addresses the implications of using compare-and-swap instructions and the design of monitors for resource allocation and access control.

Uploaded by

Paradise TheKing
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 10

Synchronization

6
CHAPTER

Tools

Exercises
6.12 The pseudocode of Figure 6.15 illustrates the basic push() and pop()
operations of an array-based stack. Assuming that this algorithm could
be used in a concurrent environment, answer the following questions:

a. What data have a race condition?


b. How could the race condition be fixed?

6.13 Race conditions are possible in many computer systems. Consider an


online auction system where the current highest bid for each item
must be maintained. A person who wishes to bid on an item calls the
bid(amount) function, which compares the amount being bid to the
current highest bid. If the amount exceeds the current highest bid, the
highest bid is set to the new amount. This is illustrated below:
void bid(double amount) {
if (amount > highestBid)
highestBid = amount;
}
Describe how a race condition is possible in this situation and what
might be done to prevent the race condition from occurring.
6.14 The following program example can be used to sum the array values
of size N elements in parallel on a system containing N computing cores
(there is a separate processor for each array element):
for j = 1 to log 2(N) {
for k = 1 to N {
if ((k + 1) % pow(2,j) == 0) {
values[k] += values[k - pow(2,(j-1))]
}
}
}

33
34 Chapter 6 Synchronization Tools

This has the effect of summing the elements in the array as a series
of partial sums, as shown in Figure 6.16. After the code has executed,
the sum of all elements in the array is stored in the last array location.
Are there any race conditions in the above code example? If so, identify
where they occur and illustrate with an example. If not, demonstrate
why this algorithm is free from race conditions.
6.15 The compare and swap() instruction can be used to design lock-free
data structures such as stacks, queues, and lists. The program example
shown in Figure 6.17 presents a possible solution to a lock-free stack
using CAS instructions, where the stack is represented as a linked list
of Node elements with top representing the top of the stack. Is this
implementation free from race conditions?

push(item) {
acquire();
if (top < SIZE) {
stack[top] = item;
top++;
}
else
ERROR
release();
}

pop() {
acquire();
if (!is empty()) {
top--;
item = stack[top];
release();
return item;
}
else
ERROR
release();
}

is empty() {
if (top == 0)
return true;
else
return false;
}

Figure 6.15 Array-based stack for Exercise 6.12.


Exercises 35

5 10 15 20 25 30 35 40
0 1 2 3 4 5 6 7

+ + + +
5 15 15 35 25 55 35 75
0 1 2 3 4 5 6 7

+ +
5 15 15 50 25 55 35 130
0 1 2 3 4 5 6 7

+
5 15 15 50 25 55 35 180
0 1 2 3 4 5 6 7

Figure 6.16 Summing an array as a series of partial sums for Exercise 6.14.

6.16 One approach for using compare and swap() for implementing a spin-
lock is as follows:

void lock spinlock(int *lock) {


while (compare and swap(lock, 0, 1) != 0)
; /* spin */
}
A suggested alternative approach is to use the “compare and compare-
and-swap” idiom, which checks the status of the lock before invoking the
compare and swap() operation. (The rationale behind this approach is
to invoke compare and swap()only if the lock is currently available.)
This strategy is shown below:

void lock spinlock(int *lock) {


{
while (true) {
if (*lock == 0) {
/* lock appears to be available */

if (!compare and swap(lock, 0, 1))


break;
}
}
}
Does this “compare and compare-and-swap” idiom work appropriately
for implementing spinlocks? If so, explain. If not, illustrate how the
integrity of the lock is compromised.
36 Chapter 6 Synchronization Tools

typedef struct node {


value t data;
struct node *next;
} Node;

Node *top; // top of stack

void push(value t item) {


Node *old node;
Node *new node;

new node = malloc(sizeof(Node));


new node->data = item;

do {
old node = top;
new node->next = old node;
}
while (compare and swap(top,old node,new node) != old node);
}

value t pop() {
Node *old node;
Node *new node;

do {
old node = top;
if (old node == NULL)
return NULL;
new node = old node->next;
}
while (compare and swap(top,old node,new node) != old node);

return old node->data;


}

Figure 6.17 Lock-free stack for Exercise 6.15.

6.17 Some semaphore implementations provide a function getValue() that


returns the current value of a semaphore. This function may, for instance,
be invoked prior to calling wait() so that a process will only call wait()
Exercises 37

while (true) {
flag[i] = true;

while (flag[j]) {
if (turn == j) {
flag[i] = false;
while (turn == j)
; /* do nothing */
flag[i] = true;
}
}

/* critical section */

turn = j;
flag[i] = false;

/* remainder section */
}

Figure 6.18 The structure of process Pi in Dekker’s algorithm.

if the value of the semaphore is > 0, thereby preventing blocking while


waiting for the semaphore. For example:

if (getValue(&sem) > 0)
wait(&sem);

Many developers argue against such a function and discourage its use.
Describe a potential problem that could occur when using the function
getValue() in this scenario.
6.18 The first known correct software solution to the critical-section problem
for two processes was developed by Dekker. The two processes, P0 and
P1 , share the following variables:

boolean flag[2]; /* initially false */


int turn;

The structure of process Pi (i == 0 or 1) is shown in Figure 6.18. The


other process is Pj (j == 1 or 0). Prove that the algorithm satisfies all
three requirements for the critical-section problem.
6.19 The first known correct software solution to the critical-section problem
for n processes with a lower bound on waiting of n − 1 turns was
presented by Eisenberg and McGuire. The processes share the following
variables:
38 Chapter 6 Synchronization Tools

while (true) {
while (true) {
flag[i] = want in;
j = turn;

while (j != i) {
if (flag[j] != idle) {
j = turn;
else
j = (j + 1) % n;
}

flag[i] = in cs;
j = 0;

while ( (j < n) && (j == i || flag[j] != in cs))


j++;

if ( (j >= n) && (turn == i || flag[turn] == idle))


break;
}

/* critical section */

j = (turn + 1) % n;

while (flag[j] == idle)


j = (j + 1) % n;

turn = j;
flag[i] = idle;

/* remainder section */
}

Figure 6.19 The structure of process Pi in Eisenberg and McGuire’s algorithm.

enum pstate {idle, want in, in cs};


pstate flag[n];
int turn;

All the elements of flag are initially idle. The initial value of turn is
immaterial (between 0 and n-1). The structure of process Pi is shown in
Figure 6.19. Prove that the algorithm satisfies all three requirements for
the critical-section problem.
Exercises 39

6.20 Explain why implementing synchronization primitives by disabling


interrupts is not appropriate in a single-processor system if the synchro-
nization primitives are to be used in user-level programs.
6.21 Consider how to implement a mutex lock using the com-
pare and swap() instruction. Assume that the following structure
defining the mutex lock is available:

typedef struct {
int available;
} lock;

The value (available == 0) indicates that the lock is available, and


a value of 1 indicates that the lock is unavailable. Using this struct,
illustrate how the following functions can be implemented using the
compare and swap() instruction:
• void acquire(lock *mutex)
• void release(lock *mutex)
Be sure to include any initialization that may be necessary.
6.22 Explain why interrupts are not appropriate for implementing synchro-
nization primitives in multiprocessor systems.
6.23 The implementation of mutex locks provided in Section 6.5 suffers from
busy waiting. Describe what changes would be necessary so that a
process waiting to acquire a mutex lock would be blocked and placed
into a waiting queue until the lock became available.
6.24 Assume that a system has multiple processing cores. For each of the
following scenarios, describe which is a better locking mechanism—a
spinlock or a mutex lock where waiting processes sleep while waiting
for the lock to become available:
• The lock is to be held for a short duration.
• The lock is to be held for a long duration.
• A thread may be put to sleep while holding the lock.
6.25 Assume that a context switch takes T time. Suggest an upper bound
(in terms of T) for holding a spinlock. If the spinlock is held for any
longer, a mutex lock (where waiting threads are put to sleep) is a better
alternative.
6.26 A multithreaded web server wishes to keep track of the number of
requests it services (known as hits). Consider the two following strate-
gies to prevent a race condition on the variable hits. The first strategy
is to use a basic mutex lock when updating hits:

int hits;
mutex lock hit lock;

hit lock.acquire();
40 Chapter 6 Synchronization Tools

#define MAX PROCESSES 255


int number of processes = 0;

/* the implementation of fork() calls this function */


int allocate process() {
int new pid;

if (number of processes == MAX PROCESSES)


return -1;
else {
/* allocate necessary process resources */
++number of processes;

return new pid;


}
}

/* the implementation of exit() calls this function */


void release process() {
/* release process resources */
--number of processes;
}

Figure 6.20 Allocating and releasing processes for Exercise 6.27.

hits++;
hit lock.release();

A second strategy is to use an atomic integer:

atomic t hits;
atomic inc(&hits);

Explain which of these two strategies is more efficient.


6.27 Consider the code example for allocating and releasing processes shown
in Figure 6.20.
a. Identify the race condition(s).
b. Assume you have a mutex lock named mutex with the operations
acquire() and release(). Indicate where the locking needs to be
placed to prevent the race condition(s).
c. Could we replace the integer variable
int number of processes = 0
with the atomic integer
atomic t number of processes = 0
Exercises 41

to prevent the race condition(s)?


6.28 Servers can be designed to limit the number of open connections. For
example, a server may wish to have only N socket connections at any
point in time. As soon as N connections are made, the server will
not accept another incoming connection until an existing connection is
released. Illustrate how semaphores can be used by a server to limit the
number of concurrent connections.
6.29 In Section 6.7, we use the following illustration as an incorrect use of
semaphores to solve the critical-section problem:
wait(mutex);
...
critical section
...
wait(mutex);
Explain why this is an example of a liveness failure.
6.30 Demonstrate that monitors and semaphores are equivalent to the degree
that they can be used to implement solutions to the same types of syn-
chronization problems.
6.31 Describe how the signal() operation associated with monitors differs
from the corresponding operation defined for semaphores.
6.32 Suppose the signal() statement can appear only as the last statement
in a monitor function. Suggest how the implementation described in
Section 6.7 can be simplified in this situation.
6.33 Consider a system consisting of processes P1 , P2 , ..., Pn , each of which has
a unique priority number. Write a monitor that allocates three identical
printers to these processes, using the priority numbers for deciding the
order of allocation.
6.34 A file is to be shared among different processes, each of which has
a unique number. The file can be accessed simultaneously by several
processes, subject to the following constraint: the sum of all unique
numbers associated with all the processes currently accessing the file
must be less than n. Write a monitor to coordinate access to the file.
6.35 When a signal is performed on a condition inside a monitor, the signaling
process can either continue its execution or transfer control to the process
that is signaled. How would the solution to the preceding exercise differ
with these two different ways in which signaling can be performed?
6.36 Design an algorithm for a monitor that implements an alarm clock that
enables a calling program to delay itself for a specified number of time
units (ticks). You may assume the existence of a real hardware clock that
invokes a function tick() in your monitor at regular intervals.
6.37 Discuss ways in which the priority inversion problem could be
addressed in a real-time system. Also discuss whether the solutions
could be implemented within the context of a proportional share
scheduler.

You might also like