3b6d96aec33c730f7dc9f849f586b8f2.ppt
- Количество слайдов: 22
Lecture Topics: 11/6 • More on threads • Synchronization problem – Too much milk • Locks
Processes and Threads • A full process includes numerous things: – an address space (defining all the code and data pages) – OS resources and accounting information – a “thread of control”, which defines where the process is currently executing (basically, the PC and registers) • Creating a new process is costly, because of all of the structures (e. g. , PCB, page tables) that must be allocated • Communicating between processes is costly, because most communication goes through the OS
Threads and Processes • Some newer operating systems (Mach, Chorus, NT) therefore support two entities: – the process, which defines the address space and general process attributes – the thread, which defines a sequential execution stream within a process • A thread is bound to a single process. For each process, however, there may be many threads. • Threads are much cheaper than processes, but they are not free
How different OS’s support threads = address space = thread example: MS/DOS example: Xerox Pilot example: Unix example: Mach, OSF, Chorus, NT
Creating Threads • What does the following print? Thread 1( void * void. Pair ) { cout << "Thread 1 " << endl; } Thread 2( void * void. Pair ) { cout << "Thread 2 " << endl; } main() { Create. Thread(Thread 1); Create. Thread(Thread 2); Sleep( 5000 ); }
Creating Threads (2) • Sleep(0) yields the CPU to another thread • What does this print? Thread 1( void * void. Pair ) { cout << "Thread 1 " << endl; } Sleep(0); Thread 2( void * void. Pair ) { cout << "Thread 2 " << endl; } Sleep(0); main() { Create. Thread(Thread 1); Create. Thread(Thread 2); Sleep( 5000 ); }
Synchronization Problem: Too Much Milk 3: 00 3: 05 3: 10 3: 15 3: 20 3: 25 3: 30 You Look in fridge; no milk Leave for store Arrive at store Buy milk Arrive home; put milk away Your Roommate Look in fridge; no milk Leave for store Arrive at store Buy milk Arrive home; put milk away Oh no!
Synchronization Definitions • Atomic Operation—an operation that cannot be interrupted (in MIPS read-modifywrite is not atomic) • Synchronization—using atomic operations to ensure cooperation among threads • Mutual Exclusion—ensuring that only one thread does a particular thing at a time. One thread doing it excludes the other and vice versa. • Critical Section—piece of code that only one thread can execute at once. • Lock—prevents someone from doing something
Modeling the Problem • Model you and your roommate as threads • “Looking in the fridge” and “putting away milk” are reading/writing a variable in memory • Current implementation YOU: YOUR ROOMMATE: // look in fridge if( milk. Amount == 0 ) { // buy milk. Amount++; }
Correctness Properties • What are the correctness properties of the “Too Much Milk” problem • Decomposed into safety and liveness – safety—the program never does anything bad – liveness—the program eventually does something good
Too Much Milk: Solution #1 • Basic idea – Leave a note when you are buying milk – Remove the note when you are done buying milk if( milk. Amount == 0 ){ if( !is. Note ){ is. Note = true; milk. Amount++; is. Note = false; } } • Why doesn’t this work? • This solution is even worse because it only fails occassionally • very hard to debug
Too Much Milk: Solution #2 • Use labeled notes so we can leave a note before checking the milk Thread A: Thread B: is. Note. A = true; if( !is. Note. B ) { if( milk. Amount == 0 ){ milk. Amount++; } } is. Note. A = false; is. Note. B = true; if( !is. Note. A ) { if( milk. Amount == 0 ){ milk. Amount++; } } is. Note. B = false; • Does this work?
Locks • A lock provides mutual exclusion • A thread must acquire the lock before entering a critical section of code • A thread releases the lock after it leaves the critical section • Only one thread holds the lock at a time • A lock is also called a mutex (for mutual exclusion) Lock: : Acquire() { while( lock. In. Use ) { // wait } lock. In. Use = true; } Lock: : Release() { lock. In. Use = false; } Naïve implementation that doesn’t work
Too Much Milk: Solution #3 • Here’s a working solution that uses a Lock Milk. Lock->Acquire(); if( milk. Amount == 0 ){ milk. Amount++; } } Milk. Lock->Release(); • Implementing Acquire and Release requires help from the CPU
Implementing Locks #1 • A flawed, but simple solution: disable interrupts to prevent a context switch Lock: : Acquire() { disable interrupts; } Lock: : Release() { enable interrupts; } • What about a multiprocessor? • Kernel can’t get control back when interrupts disabled • Critical sections may be long, turning off interrupts for a long time is bad • This approach doesn’t work for more complex synchronization primitives
Implementing Locks #2 • Better solution: only disable interrupts when updating a flag Lock: : Lock() { value = FREE; } Lock: : Release() { disable interrupts; value = FREE; enable interrupts; } Lock: : Acquire() { disable interrupts; while(value != FREE){ enable interrupts; disable interrupts; } value = BUSY; enable interrupts } • Why do we need to disable interrupts at all? • Why do we enable interrupts inside of the while loop?
Atomic Memory Operations • On a multiprocessor disabling interrupts doesn’t work • All modern processors provide an atomic read-modify-write instruction • These instructions allow locks to be implemented on a multiprocessor
Examples of Atomic Operations • Test and set (most architectures)—sets a value to 1 and returns the previous value • Exchange (x 86)—swaps value between register and memory • Compare & swap (68000)—read value, if value matches register, do exchange • Load linked and store conditional (Alpha & MIPS)—designed for load/store architectures – read value in one instruction (LL—load linked) – do some operation – when store occurs, check if value has been modified in the meantime (SC—store conditional)
Locks with Test and Set Lock: : Acquire() { Lock: : Release() { // while busy value = FREE; while( Test. And. Set(value) == 1 ) {} } } • This works, but what is the problem?
Busy Waiting • CPU cycles are consumed while thread is waiting for value to become 0 • This is very inefficient • Big problem if the thread that is waiting has a higher priority than the thread that holds the lock
Locks with Minimal Busy Waiting • Use a queue for threads waiting on the lock • A guard variable provides mutual exclusion to the queue Lock: : Acquire() { while( Test. And. Set(guard) == 1 ); if( value != FREE ) { Put self on wait queue; go to sleep, set guard = 0; } else { value = BUSY; guard = 0; } } Lock: : Release() { while( Test. And. Set(guard) == 1 ); if(anyone on wait queue){ move thread from wait queue to ready queue; } else { value = FREE; } guard = 0; }
Synchronization Summary • Threads often work independently • But sometimes threads need to access shared data • Access to shared data must be mutually exclusive to ensure safety and liveness • Locks are a good way to provide mutual exclusion • Next time we’ll see other synchronization primitives—semaphores and condition variables


