CPS 110 Thread cooperation Landon Cox Constraining concurrency

  • Slides: 44
Download presentation
CPS 110: Thread cooperation Landon Cox

CPS 110: Thread cooperation Landon Cox

Constraining concurrency êSynchronization êControlling thread interleavings êSome events are independent êNo shared state êRelative

Constraining concurrency êSynchronization êControlling thread interleavings êSome events are independent êNo shared state êRelative order of these events don’t matter êOther events are dependent êOutput of one can be input to another êTheir order can affect program results

Goals of synchronization 1. All interleavings must give correct result êCorrect concurrent program êWorks

Goals of synchronization 1. All interleavings must give correct result êCorrect concurrent program êWorks no matter how fast threads run êImportant for your projects! 2. Constrain program as little as possible êConstraints slow program down êConstraints create complexity

“Too much milk” principals

“Too much milk” principals

“Too much milk” rules ê The fridge must be stocked with milk êMilk expires

“Too much milk” rules ê The fridge must be stocked with milk êMilk expires quickly, so never > 1 milk ê Landon and Melissa êCan come home at any time êIf either sees an empty fridge, must buy milk êCode (no synchronization) if (no. Milk){ buy milk; }

Unsynchronized code will break Time 3: 00 3: 05 3: 10 3: 15 3:

Unsynchronized code will break Time 3: 00 3: 05 3: 10 3: 15 3: 20 3: 25 3: 30 3: 35 Look in fridge (no milk) Go to grocery store Look in fridge (no milk) Buy milk Go to grocery store Arrive home, stock fridge Buy milk Arrive home, stock fridge Too much milk!

What broke? êCode worked sometimes, but not always êCode contained a race condition êProcessor

What broke? êCode worked sometimes, but not always êCode contained a race condition êProcessor speed caused incorrect result êFirst type of synchronization êMutual exclusion êCritical sections

Synchronization concepts êMutual exclusion êEnsure 1 thread doing something at a time êE. g.

Synchronization concepts êMutual exclusion êEnsure 1 thread doing something at a time êE. g. 1 person shops at a time êCode blocks are atomic w/re to each other êThreads can’t run code blocks at same time

Synchronization concepts ê Critical section êCode block that must run atomically ê“with respect to

Synchronization concepts ê Critical section êCode block that must run atomically ê“with respect to some other pieces of code” ê If A and B are critical w/re to each other êThreads mustn’t interleave events in A and B êA and B mutually exclude each other ê Often conflicting code is same block êBut executed by different threads êReads/writes shared data (e. g. screen, fridge)

Back to “Too much milk” êWhat is the critical section? if (no. Milk){ buy

Back to “Too much milk” êWhat is the critical section? if (no. Milk){ buy milk; } êLandon and Melissa’s critical sections êMust be atomic w/re to each other

“Too much milk” solution 1 êAssume only atomic load/store êBuild larger atomic section from

“Too much milk” solution 1 êAssume only atomic load/store êBuild larger atomic section from load/store êIdea: 1. Leave notes to say you’re taking care of it 2. Don’t check milk if there is a note

Solution 1 code êAtomic operations êAtomic load: check note êAtomic store: leave note if

Solution 1 code êAtomic operations êAtomic load: check note êAtomic store: leave note if (no. Milk) { if (no. Note){ leave note; buy milk; remove note; } }

Does it work? if (no. Milk) { 1 if (no. Note){ 2 if (no.

Does it work? if (no. Milk) { 1 if (no. Note){ 2 if (no. Note){ leave note; 3 buy milk; 4 buy milk; remove note; } } Is this better than no synchronization at all? What if “if” sections are switched?

What broke? êMelissa’s events can happen êAfter Landon checks for a note êBefore Landon

What broke? êMelissa’s events can happen êAfter Landon checks for a note êBefore Landon leaves a note if (no. Milk) { if (no. Note){ leave note; buy milk; remove note; } }

Next solution êIdea: êChange the order of “leave note”, “check note” êRequires labeled notes

Next solution êIdea: êChange the order of “leave note”, “check note” êRequires labeled notes (else you’ll see your note)

Does it work? leave note. Landon if (no note. Melissa){ if (no. Milk){ buy

Does it work? leave note. Landon if (no note. Melissa){ if (no. Milk){ buy milk; } } remove note. Landon leave note. Melissa if (no note. Landon){ if (no. Milk){ buy milk; } } remove note. Melissa Nope. (Illustration of “starvation. ”)

What about now? while (no. Milk){ leave note. Landon if(no note. Melissa){ if(no. Milk){

What about now? while (no. Milk){ leave note. Landon if(no note. Melissa){ if(no. Milk){ buy milk; } } remove note. Landon } while (no. Milk){ leave note. Melissa if(no note. Landon){ if(no. Milk){ buy milk; } } remove note. Melissa } Nope. (Same starvation problem as before)

Next solution êWe’re getting closer êProblem êWho buys milk if both leave notes? êSolution

Next solution êWe’re getting closer êProblem êWho buys milk if both leave notes? êSolution êLet Landon hang around to make sure job is done

Does it work? leave note. Landon while (note. Melissa){ do nothing } if (no.

Does it work? leave note. Landon while (note. Melissa){ do nothing } if (no. Milk){ buy milk; } remove note. Landon leave note. Melissa if (no note. Landon){ if (no. Milk){ buy milk; } } remove note. Melissa Yes! It does work! Can you show it?

Downside of solution ê Complexity êHard to convince yourself it works ê Asymmetric êLandon

Downside of solution ê Complexity êHard to convince yourself it works ê Asymmetric êLandon and Melissa run different code ê Not clear if this scales to > 2 people ê Landon consumes CPU while waiting êBusy-waiting êNote: only needed atomic load/store

Raising the level of abstraction êMutual exclusion with atomic load/store êPainful to program êWastes

Raising the level of abstraction êMutual exclusion with atomic load/store êPainful to program êWastes resources êNeed more HW support êWill be covered later êOS can provide higher level abstractions

Course administration êProject 0 êDue soon êDo not use you late days on P

Course administration êProject 0 êDue soon êDo not use you late days on P 0 ê(only worth 2% of your grade) êSave your late days for bigger projects êSome groups have submitted êQuestions about Project 0?

Course administration void main () { // create a new list Dlist<foo> *list =

Course administration void main () { // create a new list Dlist<foo> *list = new Dlist<foo> (); // insert a new object list->insert. Front (new foo ()); How much memory should be allocated // remove and delete the object here? delete list->remove. Front (); // insert another object How much memory list->insert. Front (new foo ()); should be allocated // delete the list here? delete list; }

Course administration êProject 1 (two parts) 1. Write a small concurrent program 2. Implement

Course administration êProject 1 (two parts) 1. Write a small concurrent program 2. Implement a thread library (single CPU) êCreate new threads êSwitch between threads êManage interactions btw cooperating threads êOut on Wednesday, should be able to start ê E. g. disk scheduler input routine

Course administration ê Groups êEveryone should have a group êEveryone should have a CS

Course administration ê Groups êEveryone should have a group êEveryone should have a CS account ê Discussion section êFriday ê Office hours êTuesday, Friday ê Any other questions?

Too much milk solution leave note. Landon while (note. Melissa){ do nothing } if

Too much milk solution leave note. Landon while (note. Melissa){ do nothing } if (no. Milk){ buy milk; } remove note. Landon leave note. Melissa if (no note. Landon){ if (no. Milk){ buy milk; } } remove note. Melissa

Downside of solution ê Complexity êHard to convince yourself it works ê Asymmetric êLandon

Downside of solution ê Complexity êHard to convince yourself it works ê Asymmetric êLandon and Melissa run different code ê Not clear if this scales to > 2 people ê Landon consumes CPU while waiting êBusy-waiting êNote: only needed atomic load/store

Raising the level of abstraction êLocks êAlso called mutexes êProvide mutual exclusion êPrevent threads

Raising the level of abstraction êLocks êAlso called mutexes êProvide mutual exclusion êPrevent threads from entering a critical section êLock operations êLock (aka Lock: : acquire) êUnlock (aka Lock: : release)

Lock operations ê Lock: wait until lock is free, then acquire it do {

Lock operations ê Lock: wait until lock is free, then acquire it do { if (lock is free) { acquire lock break } } while (1) Must be atomic with respect to other threads calling this code êThis is a busy-waiting implementation êWe’ll improve on this in a few lectures ê Unlock: atomic release lock

Too much milk, solution 2 if (no. Milk) { if (no. Note){ leave note;

Too much milk, solution 2 if (no. Milk) { if (no. Note){ leave note; buy milk; remove note; } } Block is not atomic. Must atomically • check if lock is free • grab it Why doesn’t the note work as a lock?

Elements of locking 1. The lock is initially free 2. Threads acquire lock before

Elements of locking 1. The lock is initially free 2. Threads acquire lock before an action 3. Threads release lock when action completes 4. Lock() must wait if someone else has lock ê Key idea êAll synchronization involves waiting ê Threads are either running or

Too much milk with locks? lock () if (no. Milk) { buy milk }

Too much milk with locks? lock () if (no. Milk) { buy milk } unlock () êProblem? êWaiting for lock while other buys milk

Too much milk “w/o waiting”? lock () if (no. Note && no. Milk){ leave

Too much milk “w/o waiting”? lock () if (no. Note && no. Milk){ leave note “at store” Not holding unlock () buy milk lock () remove note } unlock () if (no. Note && no. Milk){ leave note “at store” unlock () buy milk lock () remove note } unlock () Only hold lock while handling shared resource.

What about this? 1 3 lock () if (no. Milk && no. Note){ leave

What about this? 1 3 lock () if (no. Milk && no. Note){ leave note “at store” unlock () buy milk stock fridge remove note } else { unlock () } 2 4 lock () if (no. Milk && no. Note){ leave note “at store” unlock () buy milk stock fridge remove note } else { unlock () }

Example: thread-safe queue enqueue () { lock (q. Lock) // ptr is private //

Example: thread-safe queue enqueue () { lock (q. Lock) // ptr is private // head is shared node *ptr; // find queue tail for (ptr=head; ptr->next!=0; ptr=ptr->next){} … ptr->next=new_element; new_element->next=0; unlock(q. Lock); } dequeue () { lock (q. Lock); element=head; // if queue non-empty if (head!=0) { // remove head=head->next; element->next=0; } unlock (q. Lock); return element; } What can go wrong?

Thread-safe queue ê Can enqueue unlock anywhere? êNo ê Must leave shared data êIn

Thread-safe queue ê Can enqueue unlock anywhere? êNo ê Must leave shared data êIn a consistent/sane state ê Data invariant enqueue () { lock (q. Lock) node *ptr; for (ptr=head; ptr->next!=NULL; ptr=ptr->next){} ê“consistent/sane state” ê“always” true ptr->next=new_element; unlock(q. Lock); // safe? new_element->next=0; }

Invariants êWhat are the queue invariants? êEach node appears once (from head to null)

Invariants êWhat are the queue invariants? êEach node appears once (from head to null) êEnqueue results in prior list + new element êDequeue removes exactly one element êCan invariants ever be false? êMust be êOtherwise you could never change

More on invariants êSo when is the invariant broken? êCan only be broken while

More on invariants êSo when is the invariant broken? êCan only be broken while lock is held êAnd only by thread holding the lock êReally a “public” invariant êWhat is the data’s state in when the lock is free êLike having a room tidy before guests arrive êHold a lock whenever manipulating shared data

More on invariants êWhat about reading shared data? êStill must hold lock êElse another

More on invariants êWhat about reading shared data? êStill must hold lock êElse another thread could break invariant ê(Thread A prints Q as Thread B enqueues)

How about this? I’m always holding a lock while accessing shared state. ptr may

How about this? I’m always holding a lock while accessing shared state. ptr may not point to tail after lock/unlock. enqueue () { lock (q. Lock) node *ptr; for (ptr=head; ptr->next!=NULL; ptr=ptr->next){} unlock (q. Lock); ptr->next=new_element; new_element->next=0; unlock(q. Lock); } Lesson: • Thinking about individual accesses is not enough • Must reason about dependencies between accesses

What about Java? Too much milk synchronized (obj){ if (no. Milk) { buy milk

What about Java? Too much milk synchronized (obj){ if (no. Milk) { buy milk } } êEvery object is a lock êUse synchronized key word êLock : “{“, unlock: “}”

Synchronizing methods public class Cubby. Hole { private int contents; public int get() {

Synchronizing methods public class Cubby. Hole { private int contents; public int get() { return contents; } public synchronized void put(int value) { contents = value; } } ê What does this mean? What is the lock? ê“this” is the lock

Synchronizing methods public class Cubby. Hole { private int contents; public int get() {

Synchronizing methods public class Cubby. Hole { private int contents; public int get() { return contents; } public void put(int value) { synchronized (this) { contents = value; } } } ê Equivalent to “synchronized (this)” block

Next class êMutual exclusion is great and all, but êIt not really expressive enough

Next class êMutual exclusion is great and all, but êIt not really expressive enough êOrdering constraints êOften must wait for something to happen êUse something called “monitors” and/or “condition variables”