Process Synchronization

 

ê   Basic Concepts 

Objectives

 

·       To introduce the critical-section problem, to ensure the consistency of shared data

·       To introduce the concept of an atomic transaction and describe mechanisms to ensure atomicity

 

Background

 

·       Concurrent access to shared data may result in data inconsistency

·       For Example:

To provide a solution to the consumer-producer problem that fills all the buffers:

We can do so by having an integer count that keeps track of the number of full buffers: 

§  Initially, count := 0.

§  It is incremented  by the producer after it produces a new buffer and

§  It is decremented by the consumer after it consumes a buffer.

 

Producer

 

while (true) {    

          /*  produce an item and put in nextProduced  */

           while (count = = BUFFER_SIZE)

                        ; // do nothing

                     buffer [in] = nextProduced;

                     in = (in + 1) % BUFFER_SIZE;

                     count++;

}  

 

Consumer

 

while (true)  {

             while (count = = 0)

                      ; // do nothing

                      nextConsumed =  buffer[out];

                       out = (out + 1) % BUFFER_SIZE;

                       count--;

                        /*  consume the item in nextConsumed

}

 

Race Condition

 

·       count++ could be implemented as

     register1 = count
     register1 = register1 + 1
     count = register1

·       count-- could be implemented as

     register2 = count
     register2 = register2 - 1
     count = register2

·       Consider this execution interleaving with “count = 5” initially:

S0: producer execute register1 = count   {register1 = 5}
S1:
producer execute register1 = register1 + 1   {register1 = 6}
S2:
consumer execute register2 = count   {register2 = 5}
S3:
consumer execute register2 = register2 - 1   {register2 = 4}
S4:
producer execute count = register1   {count = 6 }
S5:
consumer execute count = register2   {count = 4}

 

 

ê   Mutual Exclusion

 

If process  Pi  is executing in its critical section, then no other processes can be executing in their critical sections

 

Peterson’s Solution

 

·       Two process solution

·       The two processes share two variables:

ü int turn;

ü boolean ready[2];

·       The variable turn indicates whose turn is to enter the critical section. 

·       The ready array is used to indicate if a process is ready to enter the critical section.

ready[i] = true implies that process Pi is ready!

 

Algorithm for Process Pi

do {

            ready[i] = TRUE;

            turn = j;

            while (ready[j] && turn == j)  ;

 

                   critical section

     

            ready[i] = FALSE;

 

                      remainder section

 

} while (TRUE);

 

 

Examples:  

 

twothreads.c

int
main(int argc, char *argv[])
{

   pthread_t       tid0;
   pthread_t       tid1;
   pthread_attr_t  attr;
   int             i;

   pthread_attr_init(&attr);

   pthread_create(&tid0, &attr, runner0, 0);
   pthread_create(&tid1, &attr, runner1, 0);


   pthread_join(tid0, NULL);
   printf("thread 0 done\n");
   pthread_join(tid1, NULL);
   printf("thread 1 done\n");
}

void  *runner0()
{
   int             i;
   printf("Thread 0 Started\n");
   for (;;) {
      for (i = 1; i <= 3; i++) {
         printf("HI %d\n", i);
         sleep(1);
      }
   }
}

void *runner1()
{
   int             i;
   printf("Thread 1 Stareted\n");
   for (;;) {
      for (i = 0; i <= 2; i++) {
         printf("HI %c\n", i + 'a');
         sleep(1);
      }
   }
}
 

To execute:

 

     % twothreads

 

peterson.c

int             turn;
int             ready[2];

int
main(int argc, char *argv[])
{

   pthread_t       tid0;
   pthread_t       tid1;
   pthread_attr_t  attr;
   int             i;

   pthread_attr_init(&attr);

   pthread_create(&tid0, &attr, runner0, 0);
   pthread_create(&tid1, &attr, runner1, 0);

   pthread_join(tid0, NULL);
   printf("thread 0 done\n");
   pthread_join(tid1, NULL);
   printf("thread 1 done\n");
}

void *runner0()
{
   int             i;
   printf("Thread 0 Started\n");
   for (;;) {
      ready[0] = TRUE;
      turn = 1;
      while (ready[1] && turn == 1);
      for (i = 1; i <= 3; i++) {
         printf("HI %d\n", i);
         sleep(1);
      }
      printf("\n");
      ready[0] = FALSE;
   }
}

void *runner1()
{
   int             i;
   printf("Thread 1 Stareted\n");
   for (;;) {
      ready[1] = TRUE;
      turn = 0;
      while (ready[0] && turn == 0);
      for (i = 0; i <= 2; i++) {
         printf("HI %c\n", i + 'a');
         sleep(1);
      }
      printf("\n");
      ready[1] = FALSE;
   }
}
 

 

To execute:

 

     % peterson

 

 

ê   Synchronization Hardware

 

·       Many systems provide hardware support for critical section code

·       Modern machines provide special atomic hardware instructions (non-interruptible).

Examples:

ü Test and  set  memory value.

ü swap contents of two memory words.

 

Solution to Critical-section Problem Using Locks

 

do {

        acquire lock

                critical section

        release lock

                remainder section

} while (TRUE);

 

 

ü TestAndSet Instruction

 

boolean TestAndSet (boolean *target)

{

boolean rv = *target;

*target = TRUE;

return rv:

}

 

Solution using TestAndSet

 

  Shared boolean variable lock := FALSE;

.

  do {

while ( TestAndSet (&lock ) ) ;  

 

//    critical section

 

lock = FALSE;

 

//      remainder section

 

} while (TRUE);

 

 

ü Swap  Instruction

 

void Swap (boolean *a, boolean *b)

{

boolean temp = *a;

*a = *b;

*b = temp:

}

 

Solution using Swap

Shared Boolean variable lock := FALSE;

Each process has a local Boolean variable key

 

do {

 

key = TRUE;

while ( key == TRUE) Swap (&lock, &key );

critical section

lock = FALSE;

 

remainder section

 

} while (TRUE);

 

 

ê   Semaphore 

Semaphore S – integer variable

Two standard operations modify S: wait (S)  & signal (S)

 

Originally called (by the late Dijkstra):   P  & V

 

wait (S) {

while ( S <= 0); /*busy wait*/

S--;

}

 

signal (S) {

S++;

    }

 

Mutual Exclusion

 

Semaphore mutex = 1;   

 

do {

 

     wait (mutex);

Critical Section

     signal (mutex);

 

Remainder Section

 

} while (TRUE);

 

Semaphore implementation must guarantee that no two processes

can execute wait () and signal () on the same semaphore at the same time.

 

 

 

ê   Examples:

 

ThreadMutex.c

 

void           *functionC();
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_t       thread1, thread2;
int             counter = 0;
int             lockone = 0;
int             lockall = 0;
int             nolock = 0;

main(int argc, char *argv[])
{
  int             rc1, rc2;
  int             mode;

  if (argc == 1) {
       printf("Usage is ThreadMutex [0|1|2]\n");
       printf("0 - no lock\n1 - lock one by one\n2 - lock all\n");
       exit(0);
  }
  mode = atoi(argv[1]);
  if (mode == 0)        nolock = 1;
  else if (mode == 1)   lockone = 1;
  else if (mode == 2)   lockall = 1;

  pthread_create(&thread1, NULL, &functionC, NULL);

  pthread_create(&thread2, NULL, &functionC, NULL);


  pthread_join(thread1, NULL);
  pthread_join(thread2, NULL);
  printf("Counter value: %d\n", counter);

  exit(0);
}

void *functionC()
{
  int             i;
  if (lockall) {
       pthread_mutex_lock(&mutex1);
       for (i = 1; i <= 1000000; i++) {
           counter++;
           if (i % 100000 == 0)
              printf(" Thread (%d): i is: %d and counter is: %d \n",

                       pthread_self() -1, i, counter);
       }
       pthread_mutex_unlock(&mutex1);
  }

  else

  if (lockone) {
       for (i = 1; i <= 1000000; i++) {
           pthread_mutex_lock(&mutex1);
           counter++;
           if (i % 100000 == 0)
              printf(" Thread (%d): i is: %d and counter is: %d \n",

                       pthread_self() -1, i, counter);
           pthread_mutex_unlock(&mutex1);
       }
  } else

  if (nolock) {
       for (i = 1; i <= 1000000; i++) {
           counter++;
           if (i % 100000 == 0)
              printf(" Thread (%d): i is: %d and counter is: %d \n",

                       pthread_self() -1, i, counter);
       }
  }
}

 

To execute:

 

% ThreadMutex 0 (or 1 or 2)

 

 

 

 

ThreadMutexJava

 

public class ThreadMutexJava extends Thread {

  private static int mode = 1;

  private static int countUp = 0;

  private static int threadCount = 0;

  private int threadNumber = ++threadCount;

 

  private static  synchronized void syninc () {

          countUp++;

  }

 

  private static  void inc () {

          countUp++;

  }

 

  public ThreadMutexJava() {

    System.out.println("Making " + threadNumber);

  }

 

  public void run() {

    int i;

    for (i=1; i<= 1000000; i++) {

      if (mode == 1)

      syninc();

      else

      inc();

      if (i % 100000 == 0)

          System.out.println (" Thread#: " + threadNumber +

                              " i: " + i + 

                              " counter: " + countUp);

    }

}

 

public static void main(String[] args) throws InterruptedException  {

    ThreadMutexJava.mode = Integer.parseInt(args[0]);

    Thread t1 = new ThreadMutexJava();

    Thread t2 =  new ThreadMutexJava();

    t1.start();

    t2.start();

    System.out.println("All Threads Started");

    t1.join();

    t2.join();

    System.out.println("All Threads Ended: " + ThreadMutexJava.threadCount);

    System.out.println("countUp final value is:  " +  ThreadMutexJava.countUp);

  }

}

 

To execute:

 

% java ThreadMutexJava  0 (or 1 )

 

 

Semaphore Implementation with no busy waiting

 

·       With each semaphore there is an associated waiting queue.

·       Each semaphore has two data items:

ü value (of type integer)

ü pointer to next process in the waiting queue.

·       Two operations:

ü block – place the process invoking the operation on the  appropriate waiting queue.

ü wakeup – remove one of processes in the waiting queue and place it in the ready queue.

 

Implementation of wait:

           

                  wait (semaphore *S) {

                             S -> value--;

                             if (S -> value < 0) {

                                      add this process to S -> list;

                                      block();

                             }

                   }

 

Implementation of signal:

 

                   signal (semaphore *S) {

                             S -> value++;

                             if (S -> value <= 0) {

                                      remove a process P from S ->list;

                                      wakeup(P);

                             }

                   }

 

Deadlock and Starvation

 

·       Deadlock – two or more processes are waiting indefinitely for an event that can be caused by only one of the waiting processes

·       Let S and Q be two semaphores initialized to 1

 

 P0                                                                 P1

wait (S);                                                                wait (Q);

wait (Q);                                                               wait (S);

.                                                                  .

.                                                                  .

.                                                                  .

        signal  (S);                                                            signal (Q);

        signal (Q);                                                             signal (S);

 

ê   Classical Problems of Synchronization 

                            I.            Bounded-Buffer

                       II.            Readers-Writers

                  III.            Dinning-Philosophers

 

 

                I.            Bounded-Buffer Problem

 

ü N buffers, each can hold one item

ü Semaphore mutex initialized to the value 1

ü Semaphore full initialized to the value 0

ü Semaphore empty initialized to the value N.

 

 

The producer process

     

           do  {

 

                         //   produce an item

                   wait (empty);

                   wait (mutex);

                         //  add the item to the  buffer

                    signal (mutex);

                    signal (full);

         

        } while (TRUE);

 

 

The consumer process

          

         do {

 

                    wait (full);

                    wait (mutex);

                             //  remove an item from  buffer

                    signal (mutex);

 

                    signal (empty);            

                            //  consume the item

         

        } while (TRUE);

 

 

ê   Examples:

 

ProducerConsumer.c

 

pthread_mutex_t produce_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t consume_mutex = PTHREAD_MUTEX_INITIALIZER;

int             b;      /* buffer size = 1; */

main()
{
  pthread_t       producer_thread;
  pthread_t       consumer_thread;
  void           *producer();
  void           *consumer();

  pthread_mutex_lock(&consume_mutex);  /* to make sure that producer goes first */
  pthread_create(&consumer_thread, NULL, consumer, NULL);
  pthread_create(&producer_thread, NULL, producer, NULL);
  pthread_join(consumer_thread, NULL);
}


add_buffer(int i)
{
  b = i;
}


get_buffer()
{
  return b;
}

producer()
{
  int             i;
  for (i = 0; i < 10; i++) {
       pthread_mutex_lock(&produce_mutex);
       add_buffer(i);
       pthread_mutex_unlock(&consume_mutex);
       printf("put %d\n", i);
  }
  pthread_exit(NULL);
}


consumer()
{
  int             i, v;
  for (i = 0; i < 10; i++) {
       pthread_mutex_lock(&consume_mutex);
       v = get_buffer();
       pthread_mutex_unlock(&produce_mutex);
       printf("got %d\n", v);
  }
  pthread_exit(NULL);
}

 

To execute:

 

% ProducerConsumer

 

 

ProducerConsumerJava

 

 

 

class           PCBuffer {

  private int     contents;

  private boolean available = false;

 

  public synchronized int syncget() {

       while (available == false) {

           try {

               wait();

           } catch(InterruptedException e) {}

       }

       available = false;

       System.out.println("Consumer got: " + contents);

       notifyAll();

       return contents;

  }

  public synchronized void syncput(int value) {

       while (available == true) {

           try {

               wait();

           } catch(InterruptedException e) {}

       }

       contents = value;

       available = true;     

       System.out.println("Producer put: " + contents);

       notifyAll();

  }

}

 

 

class Producer extends Thread {

  private PCBuffer Item;

  public          Producer(PCBuffer c) {

       Item = c;

  }

  public void     run() {

       for (int i = 0; i < 10; i++) {

            Item.syncput(i);

            try {

                sleep(1000);

            }   catch(InterruptedException e) { }

 

       }

  }

}

 

class Consumer extends Thread {

  private PCBuffer Item;

  public          Consumer(PCBuffer c) {

       Item = c;

  }

  public void     run() {

       int             value = 0;

       for             (int i = 0; i < 10; i++) {

           value = Item.syncget();

      }

  }

}

 

public class    ProducerConsumerJava {

  public static void main (String[] args) {

       PCBuffer        Item = new PCBuffer();

       Producer        p1 = new Producer(Item);

       Consumer        c1 = new Consumer(Item);

       p1.start();

       c1.start();

  }

}

 

To execute:

 

% java ProducerConsumerJava 

 

 

ProducerConsumerSemaphore.c

 

 

#define BUFFER_SIZE 10

/* The mutex lock */
pthread_mutex_t mutex;

/* the semaphores */
sem_t           full;
sem_t          
empty;


int
             b[BUFFER_SIZE];       
int             psleep, csleep;


main()
{
    pthread_t       producer_thread;
    pthread_t       consumer_thread;
    void           *producer();
    void           *consumer();

    psleep = atoi(argv[1]);

    csleep = atoi(argv[2]);


   /* Create the mutex lock */
   pthread_mutex_init(&
mutex, NULL);


   /* Create the full semaphore and initialize to 0 */
   sem_init(&
full, 0, 0);


   /* Create the empty semaphore and initialize to BUFFER_SIZE */
   sem_init(&
empty, 0, BUFFER_SIZE);

    pthread_create(&consumer_thread, NULL, consumer, NULL);
    pthread_create(&producer_thread, NULL, producer, NULL);
    pthread_join(consumer_thread, NULL);
}

int in = 0;
void add_buffer(int i)
{
    b[in] = i;
    in = (in + 1) % BUFFER_SIZE;

}

int out = 0;
int get_buffer()
{
     int value;
     value = b[out];
     out = (out + 1) % BUFFER_SIZE;
     return value;
}

producer()
{
    int             i = 0;
    int ranvalue;
    for (;;) {

 

       ranvalue = 1 + (rand() % psleep);
       sleep(ranvalue);

       sem_wait(&empty);               //acquire the empty lock


         pthread_mutex_lock(&mutex);     //acquire the mutex lock
         
add_buffer(i);                  // CRITICAL SECTION
         printf(">>put %d\n", i++);

pthread_mutex_unlock(&mutex);   //release the mutex lock


       sem_post(&full);                //signal full
        

       
    }
    pthread_exit(NULL);
}

 

consumer()
{
int ranvalue;
    int  v;
    for (;;) {


       sem_wait(&full);               //aquire the full lock


       pthread_mutex_lock(&mutex);     //aquire the mutex lock
      
v = get_buffer();               //CRITICAL SECTION

       printf("<<got %d\n", v);
       pthread_mutex_unlock(&mutex);   //release the mutex lock


       sem_post(&empty);               //signal empty
        

       ranvalue = 1 + (rand() % csleep);
       sleep(ranvalue);
    }
    pthread_exit(NULL);
}

 

To execute:

 

% ProducerConsumerSemaphore   5   5

% ProducerConsumerSemaphore   5   4

% ProducerConsumerSemaphore   4   5

 

 

 

 

           II.            Readers-Writers Problem

 

·       A data set is shared among a number of concurrent processes

ü Readers – only read the data set; they do not perform any updates

ü Writers   – can both read and write

·       Allow multiple readers to read at the same time. 

·       Only one single writer can access the shared data at the same time

·       Shared Data

ü Data set

ü Semaphore mutex initialized to 1

ü Semaphore wrt initialized to 1

ü Integer readcount initialized to 0

 

The Writer process

       

              do {

 

                        wait (wrt)         

                             writing is performed

                        signal (wrt) ;

 

             } while (TRUE);

 

 

The Reader process

       

      do {

 

                     wait (mutex) ;

                             readcount ++ ;

                             if (readcount == 1)        //First Reader, keep writers out

                                          wait (wrt) ;

                       signal (mutex)

 

                                  reading is performed

 

                        wait (mutex) ;

                                readcount  - - ;

                                if (readcount  == 0)     //Last Reader, allow in writers

                                           signal (wrt) ;

                      signal (mutex) ;

 

        } while (TRUE);

 

      III.            Dining-Philosophers Problem

 

 

Shared data

§  Bowl of rice (data set)

§  Semaphore chopstick [5] initialized to 1

 

Philosopher i:

 

do  {

            wait ( chopstick[i] );

            wait ( chopStick[ (i + 1) % 5] );

 

                   Eat

 

            signal ( chopstick[i] );

            signal (chopstick[ (i + 1) % 5] );

 

                  Think

 

} while (TRUE);

 

Examples:

 

DinningPhilosopher.c   

 

#define   N   5
pthread_mutex_t   mymutex[N];


void            Pickup(int);
void            Putdown(int);

 

philosopher(void *arg)
{
    int             sleeptime;
    int             i = pthread_self() - 1;
    srand((unsigned) time(0));

    while (1) {
         sleeptime = 1 + rand() % N;
         printf("\nPhilosopher  %d is THINKING for %d seconds\n", i, sleeptime);
         sleep(sleeptime);

         Pickup(i);
         sleeptime = 1 + rand() % N;
         printf("\n   >> Philosopher  %d is EATING
for %d seconds\n", i, sleeptime);
         sleep(sleeptime);
         Putdown(i);
    }
}

Pickup(int i)
{
         pthread_mutex_lock(&mymutex[(i) % N]);

usleep(slowdown);
         pthread_mutex_lock(&mymutex[(i + 1) % N]);
}

Putdown(int i)
{
         pthread_mutex_unlock(&mymutex[(i) % N]);
         pthread_mutex_unlock(&mymutex[(i + 1) % N]);
}

main(int argc, char *argv[])
{
    int             rc, i;
    int             status;

    pthread_t       threads[N];
    pthread_attr_t  attr;

   if (argc > 1) slowdown = atoi(argv[1]);
    for (i = 0; i < N; i++) {
         pthread_mutex_init(&mymutex[i], NULL);
    }
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    for (i = 0; i < N; i++) {
         pthread_create(&threads[i], NULL, philosopher, NULL);
    }
    pthread_exit(NULL);
}

 

 

To execute:

 

% DinningPhilosopher

 

To casuse deadlock:

% DinningPhilosopher  5    // slowdown picking the 2nd fork

 

DinningPhilosopherJava  

 

 

class           Forks {

 

    public int [] available = {1, 1, 1, 1, 1};

 

    public synchronized void pickfork(int i) {

        while (available[i] == 0) {

            try {

                wait();

            } catch(InterruptedException e) {

            }

        }

        available[i] = 0;

    }

 

    public synchronized void putfork(int i) {

        available[i] = 1;

        notifyAll();

    }

}

 

class Philosopher extends Thread {

    private Forks   fks;

    private int     index;

    private int     sleeptime;

 

    static int     slowdown = 0;

 

    public          Philosopher(Forks f, int number) {

        fks = f;

        index = number;

    }

 

    public void     run() {

        for (;;) {

            try {

                sleeptime = 1 + (int) (Math.random() * 5);

        System.out.println("\nPhilosopher " + index +

                     " is thinking for " + (int) sleeptime + " seconds\n");

                sleep(sleeptime * 1000);

 

                Pickup(index);

                sleeptime = 1 + (int) (Math.random() * 5);

    System.out.println("\n   >> Philosopher " + index +

                " is EATING for " + (int) sleeptime + " seconds\n");

                sleep(sleeptime * 1000);

                Putdown(index);

 

            }   catch(InterruptedException e) {

            }

        }

    }

    public void     Pickup(int i) {

            fks.pickfork((i) % 5);

            try {

                sleep(slowdown * 1000);

            }   catch(InterruptedException e) {}

            fks.pickfork((i + 1) % 5);

    }

    public void     Putdown(int i) {

            fks.putfork((i) % 5);

            fks.putfork((i + 1) % 5);

    }

}

 

public class    DinningPhilosopherJava {

 

    public static void main(String[] args) {

 

        if (args.length == 1)

          Philosopher.slowdown = Integer.parseInt(args[0]);

 

        Forks           fk = new Forks();

        for (int i = 0; i <= 4; i++) {

            Philosopher     p = new Philosopher(fk, i);

            p.start();

        }

    }

}

 

To execute:

 

% java DinningPhilosopherJava

 

To casuse deadlock:

% java DinningPhilosopherJava  5  // slowdown picking 2nd fork

 

 

 


ê   Atomic Transactions 

 

·       A collection of instructions or read /write operations that performs single logical function.

·       Terminated by commit  (transaction successful) or abort (transaction failed) operation

·       Aborted transaction must be rolled back to undo any changes it performed

 

 

Types of Storage Media

 

·       Volatile storage – information stored does not survive system crashes

Example:  main memory, cache

·       Nonvolatile storage – Information usually survives crashes

Example:  disk and tape

·       Stable storage – Information never lost

Not actually possible, so approximated via replication to devices with independent failure modes

 

Log-Based Recovery

·       Record to stable storage information about all modifications by a transaction

·       Each  record describes single transaction write operation including:

ü Transaction name

ü Data item name

ü Old value

ü New value

·       <Ti  starts>      written to log when transaction Ti starts

·       <Ti  commits> written when Ti commits

 

Log-Based Recovery Algorithm

                      ·          Using the log, system can handle any volatile memory errors

ü Undo(Ti) restores value of all data updated by Ti

ü Redo(Ti) sets values of all data in transaction Ti to new values

                      ·          Undo(Ti) and redo(Ti) must be idempotent

i.e., multiple executions must have the same result as one execution

                      ·          If system fails, restore state of all updated data via log

ü If log contains <Ti starts> without <Ti commits>, Undo(Ti)

ü If log contains <Ti starts> and <Ti commits>, Redo(Ti)

Checkpoints

·       Checkpoints shorten log and recovery time.

·       Checkpoint scheme:

ü Output all log records currently in volatile storage to stable storage

ü Output all modified data from volatile to stable storage

ü Output a log record <checkpoint> to the log on stable storage

·        Recovery only includes Ti, such that Ti started executing before the most recent checkpoint.

 

ê   Concurrent Transactions

Transaction execution must be equivalent to serial execution – serializability

Concurrency-control algorithms provide serializability

Consider two data items A, B and Transactions T0 , T1

Schedule 1:   T0 then T1

Schedule 2:  Concurrent Serializable Schedule

Locking Protocol to ensure serializabilty

·       Ensure serializability by associating lock with each data item

·       Locks

ü Shared:       Ti  has share lock on item Q,   then Ti can:         read  Q but not write Q

ü Exclusive:   Ti  has exclusive lock  on item Q,   then Ti can: read  Q   and    write Q

·       Require every transaction on item Q acquire appropriate lock using: 

 

Two-phase Locking Protocol:

Each transaction issues lock and unlock requests in two phases

ü Growing    obtaining locks

ü Shrinking – releasing locks