You are expected to understand this. CS 111 Operating Systems Principles, Spring 2006
You are here: CS111: [[2006spring:notes:lec9]]
 
 
 

Lecture 9 notes

Read Write Locks Part Deux

Basic Read Write Lock

Technique Implement a Lock that will allow Multiple readers to access a certain memory value, or a single writer to modify a certain memory value.

Motivation If there are multiple readers, the data should not be able to be modified by a writer. But, by allowing multiple readers it is possible to do more work instead of just blocking any other readers. Allowing multiple writers leads to unpredicted behavior, as well as having a reader and writer active at the same time. As a result, there is no need to block if there are either multiple readers or one writer.

Implementation It is necessary to maintain a READ COUNT variable that is protected by a mutex (semaphore)

Write_lock ()

{
	P(m)			// lock
	while ( NumReaders > 0 )
	{
		V(m)		// unlock
		P(m)		// lock
	}
}

But wait - we are polling which is bad!!!

Solution - We can use a Write Exclusion Semaphore which allows writers to block until there are no more active readers.

Symbol Table

M mutex
NR read count
WX write exclusion
Read_lock ()

{
P(M)
if (++NR == 1)
	P(WX)
V(M)
}
Read_unlock()
{
P(M)
if (--NR == 0)
	V(WX)
V(M)
}
Write_unlock ()
{
V(WX)
}
Write_lock ()
{
P(WX)
}

Note that this implementation priviledges readers; if there is one writer and a thousands of readers, the writer will not get to have the lock.

Read Write Lock Table 1 - Basic Operation
Proccess1 Process 2 Mutex ReadCount Write Exclusion
initial status initial status 1 0 1
read_lock 1 1 0
/*executing write_lock 1 1 0
read_unlock /*blocked*/ 1 0 1
/*w_lock unblocks*/ 1 0 0
read_lock /*executing*/ 0 1 0
/*blocked*/ /*executing*/ 0 1 0
/*blocked*/ write_unlock 0 1 1
/*r_lock unblocked*/ 1 1 0
/*executing*/ 1 1 0
read_unlock 1 0 1

Note that in the table, the write_lock operation are waiting for all read operations to finish. This indicates a preference towards readers.

Read Write Locks Part 3: Prefering Writers
M mutex
NR read count
WX write exclusion
M2 mutex
NW writer count
RX read exclusion

Note: new code is commented.

Read_lock()
{
P(RX)	//new
P(M)
if (++NR == 1)
   P(WX)
V(M)
V(RX)	//new
}
Read_unlock()
{
P(M)
if (--NR == 0)
	V(WX)
V(M)
}
Write_unlock()
{
V(WX)
P(M2)	//new
if (--NW == 0)	//new
	V(RX) //new
V(M2)	//new
}
Write_lock()
{
P(M2)	//new
if (++NW ==1)	//new
	P(RX)	//new
V(M2)	//new
P(WX)
}

Explanation
The behavior here is that reader exclusion allows multiple readers and write exclusion allows one write at a time. This is seen in the placement of both P(RX) and V(RX) in the read lock code, and staggering the P(WX) and V(WX) across the write lock and unlock code.

Read Write Lock Table 2
Action M NR WX M2NWRX
Initial values101101
P1 readlock 1 1 0 1 0 1
P2 writelock 1 1 0 1 1 0
P3 readlock 1 1 0 1 1 0
P1 readunlock 1 1 1 1 1 0
unblock P2 1 0 0 1 1 0
P2 writeunlock 1 0 1 1 0 1
unblock P3 1 0 0 1 0 1

Conditional Variables

What is it? A condition variable allows a process to block until a specified condition is fulfilled. This condition is often represented as Boolean expression.

Examples of conditions – hell freezes over, some integer i < 100, etc.

Condition variables turn this expression into an object.

Implementation using Semaphore

This requires multiple semaphores as well as a waitcounter. We can't use a single semaphore because wait always blocks unlike P(), and signal does not behave like V().

Initial Values
M mutex1
W number of waiters 0
E event semaphore 0

Events have 2 operations: wait() and signal().

 wait(E)
// blocks until a signal happens or is raised

{
    P(M);
    ++NW;
    V(M);
    P(E);
}
signal(E)
//wake up 1 waiter, if there are any
//otherwise, nothing happens (signal goes away)
{
    P(M)
    if ( NW > 0 )
    {
        NW--
        V(E)
    }
    V(M)
}
How to use condition variables

process 1

...
/*do some stuff*/
wait(Hell_is_frozen);
/* Brrrr, Satan needs a sweater!*/
...

process 2

...
freeze (Hell);
signal(Hell_is_frozen);
...

Note that it is the application's responsibility to raise a signal when a condition is met.

Introducing Mutual Exclusion to Condition Variables

Avoid race conditions by adding mutex operations atomically.

Wait(CV, M) 
{
	//these next two operations are now atomic
        Unlock(M)	//usually called this way because we have Mutex at this time
	Event.wait (CV)

	Lock (M)
}
Signal(CV)
{
	Event signal(CV)
}
Broadcast(CV)
//like signal, but wakes up __all__ threads waiting on CV

For implementation of the above, look at pthead_cond_var_t and pthread_mutext_t objects.

Barriers

Barriers are a type of synchronization method that blocks threads until a specified number of threads have been blocked, and then unlocks all the threads.

Motivation example – Matrix Math

(M1* M2) + (M3*M4) + (M5*M6)

Thread 1 mutiplies M1 with M2, then blocks.
Thread 2 multiplies M3 with M4, then blocks.
Thread 3 multiplies M5 with M6, then blocks.
After all three threads are blocked, the barrier unblocks them, and the result can be added.

.:lec9_box.jpg

Barrier(B) there are N threads. Block until all N threads are blocked in barrier B, then unblock all threads.

Bounded Buffer/ Max Capacity Queue Problem

In this section, we will demonstrate the use of different sychrnization techniques with a Queue with a Max Capacity. For Reference, the handout given out in class is available here.

Basic Queue

Queue Operations

enq(Q,E)
// if there is space fore E, enqueue it on Q
E = deq(Q)
/*nonblocking method*/
//////////////////////
//dequeue from Q and return first element of Queue
//if Q is empty, fail

/*blocking method*/
///////////////////
//block until the queue is not empty
//then dqueue and return the first element

Labeling Critical Sections

Lock-Free Queue

Why? If we have at most one dequeuer and one enqueuer, the enqueuer will always access the head of the queue, and the dequeuer will always access and modify the tail of the queue. If we limit ourselves to 1 active dequeuer and enqueuer, the only case that can be troublesome is if the queue is empty. As a result, we can implement a lock-free queue and bypass the overhead costs of locking and unlocking.

Idea If there is at most one reading thread and at most one writing thread, it is safe to avoid locks if each variable is written by one thread.

Implementation Given a size, head, and tail pointers, the head is only written by deq(...), the tail is only written by enq(...). In this way, there should not be interference amongst the threads because pointers accessible by running threads are mutually exclusive.

This page's Authors

--- Jay Mencio 2006/05/08 21:59

--- Julia Uskolovsky 2006/05/08 22:01

 
2006spring/notes/lec9.txt · Last modified: 2006/09/26 11:42 (external edit)
 
Recent changes RSS feed Driven by DokuWiki