Lecture 10: Concurrency & Locks - Milk Problem Solution & Lock Implementation, Slides of Advanced Operating Systems

The solution to the too much milk problem using locks and discusses different ways of implementing locks, including with test and set instructions. The document also introduces semaphores as a higher-level synchronization primitive.

Typology: Slides

2011/2012

Uploaded on 08/06/2012

dharmesh
dharmesh 🇮🇳

4.1

(9)

87 documents

1 / 18

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Lecture No. 10
docsity.com
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12

Partial preview of the text

Download Lecture 10: Concurrency & Locks - Milk Problem Solution & Lock Implementation and more Slides Advanced Operating Systems in PDF only on Docsity!

  • Lecture No.

Overview of today’s lecture

 Concurrency examples (cont’d from previous

lecture)

 Locks

 Implementing locks with disabling interrupts

 Implementing locks with busy waiting

 Implementing locks with test and set like low-level

hardware instructions

 Semaphores—Introduction and definition

 Re-cap of lecture

Too Much Milk Summary

 Solution #3 works, but it's really unsatisfactory:  1. really complicated -- even for this simple an example, hard to convince yourself it really works

 2. A's code different than B's -- what if lots of threads? Code would have to be slightly different for each thread.

 3. While A is waiting, it is consuming CPU time ( busywaiting )  There's a better way: use higher-level atomic operations; load and store are too primitive.

Locks

Lock: prevents someone from doing something.

  1. Lock before entering critical section, before accessing shared data
  2. unlock when leaving, after done accessing shared data
  3. wait if locked

Lock::Acquire -- wait until lock is free, then grab it

Lock::Release -- unlock, waking up a waiter if any

These must be atomic operations -- if two threads are waiting for the lock, and both see it's free, only one grabs it! With locks, the too much milk problem becomes really easy!

lock->Acquire();

if (nomilk)

buy milk;

lock->Release();

A flawed, but very simple implementation

Lock::Acquire() { disable interrupts;} Lock::Release() { enable interrupts; }

  1. Critical section may be in user code, and you don't want to allow user code to disable interrupts (might never give CPU back!). The implementation of lock acquire and release would be done in the protected part of the operating system, but they could be called by arbitrary user code.
  2. Might want to take interrupts during critical section. For instance, what if the lock holder takes a page fault? Or does disk I/O?
  3. Many physical devices depend on real-time constraints. For example, keystrokes can be lost if interrupt for one keystroke isn't handled by the time the next keystroke occurs. Thus, want to disable interrupts for the shortest time possible. Critical sections could be very long running. .

Busy-waiting implementation

class Lock {int value = FREE;

Lock::Acquire() { Disable interrupts; while (value != FREE) { Enable interrupts; // allow interrupts Disable interrupts; }

value = BUSY;Enable interrupts;}

Lock::Release() {Disable interrupts;value = FREE;Enable interrupts;} }

Implementing without busy-waiting (1)

 Lock::Acquire()  { Disable interrupts; while (value != FREE) { put on queue of threads waiting for lock change state to sleeping or blocked } value = BUSY; Enable interrupts;  }  Lock::Release()  { Disable interrupts; if anyone on wait queue { take a waiting thread off put it on ready queue change its state to ready } value = FREE; Enable interrupts; }

Implementing without busy-waiting (2)

 When does Acquire re-enable interrupts :

 In going to sleep?

 Before putting the thread on the wait queue?

 Then Release can check queue, and not wake the thread up.

 After putting the thread on the wait queue, but before going to

sleep?

 Then Release puts thread on the ready queue, When thread

wakes up, it will go to sleep, missing the wakeup from Release.

 In other words, putting the thread on wait queue and going to

sleep must be done atomically before re-enabling interrupts

Implementing locks with test&set

 Test&set reads location, sets it to 1, and returns old value.  Initially, lock value = 0;  Lock::Acquire {  while (test&set(value) == 1)  ; // Do nothing  }  Lock::Release {  value = 0;  }

 If lock is free, test&set reads 0 and sets value to 1, so lock is now busy. It returns 0, so Acquire completes. If lock is busy, test&set reads 1 and sets value to 1 (no change), so lock stays busy, and Acquire will loop. This is a busy-wait loop, but as with the discussion above about disable interrupts, you can modify it to sleep if lock is BUSY.

Semaphores

 semaphore = a synchronization primitive

  • higher level than locks
  • invented by Dijkstra in 1968, as part of the THE OS

 A semaphore is:

  • a variable that is manipulated atomically through two operations, signal and wait
  • wait(semaphore): decrement, block until semaphore is open also called P(), after Dutch word for test, also called down()
  • signal(semaphore): increment, allow another to enter also called V(), after Dutch word for increment, also called up()

A pseudocode implementation

Two types of Semaphores

 Binary semaphore (aka mutex semaphore)

  • guarantees mutually exclusive access to resource
  • only one thread/process allowed entry at a time
  • counter is initialized to 1

 Counting semaphore (aka counted semaphore)

  • represents a resources with many units available
  • allows threads/process to enter as long as more units are available
  • counter is initialized to N

 N = number of units available

 Only operations are P and V -- can't read or write value, except to set it initially

 Operations must be atomic -- two P's that occur together

can't decrement the value below zero.