cpl 2016 week 2
play

CPL 2016, week 2 Inter-thread synchronization: locks and monitors - PowerPoint PPT Presentation

CPL 2016, week 2 Inter-thread synchronization: locks and monitors Oleg Batrashev Institute of Computer Science, Tartu, Estonia February 15, 2016 Agenda Inter-thread synchronization Atomicity and consistency Unconditional locking Locking


  1. CPL 2016, week 2 Inter-thread synchronization: locks and monitors Oleg Batrashev Institute of Computer Science, Tartu, Estonia February 15, 2016

  2. Agenda Inter-thread synchronization Atomicity and consistency Unconditional locking Locking patterns and problems Simple patterns Deadlocks Refining locks Conditional locking Cost of synchronization

  3. Atomicity It is sometimes needed for a block of code ◮ to run operations atomically , i.e. “all at once” ◮ other threads do not interfere: ◮ this thread sees all values as frozen at the start of the block ◮ other threads do not see changes until the whole block finishes ◮ as-if-serial execution

  4. Atomicity for single variable Increment a variable from several threads volatile int c = 0 ... c = c+1; // or ++c; One possible order of actions (the variable is read into register) // Thread1 // Thread2 r1 = c r2 = c r2 = r2 + 1 c = r2 r = r1 + 1 c = r1 is incorrect, because it results in increment by one: c=c+1 . The solution: ◮ prohibit other threads from changing the variable during read/increment/write

  5. Atomic variables Easiest way to solve the above problem is to use atomic variables AtomicInteger c = new AtomicInteger (0); ... c. incrementAndGet () ◮ atomically increments the variable Other atomic classes and methods: ◮ AtomicBoolean , AtomicLong , AtomicReference ◮ getAndSet(V) – no values are lost in updates, important for object de-initialization ◮ compareAndSet(expect, update) – do not update if something is different, important if the state has changed ◮ weakCompareAndSet(expect, update) – no ordering guarantees for other variables! ◮ lazySet() – eventually sets the value

  6. Atomicity for multiple variables Buffer with internal array and size fields: void addElement (Element e) { size += 1; array[size -1] = e; } Someone may try to read the last element which is not yet set: Element getLastElement () { return array[size -1]; } ◮ reordering the two statements in addElement helps only if: ◮ size is volatile and you update it last in the addElement ! ◮ there is only single writer.

  7. Consistency An object may have only limited set of consistent states: ◮ its fields must correspond to each other ◮ e.g. having matching array and size fields in the buffer An action could have ◮ precondition : must initially see the object in consistent state ◮ postcondition : must leave the object in consistent state There are extra conditions may be available as will be shown in Conditional Locks ◮ consistency is the responsibility of the application ◮ concurrency makes it more difficult to provide consistency

  8. Optimistic vs Pessimistic locking Transition between consistent states may happen with two methods: ◮ pessimistic : make sure nobody interferes and then run the action ◮ optimistic : run the action locally and publish the results if nobody has interfered, otherwise restart This corresponds to: ◮ locks (mutexes) – the topic of this section ◮ transactions – the topic to be studied with Software Transaction Memory in Clojure We continue with pessimistic locking

  9. Locks Locks are used to manage access to critical sections: ◮ lock is an entity that may be acquired and released ◮ only one thread may acquire the same lock at a time ( mutex ) ◮ a thread must wait if another thread is holding the lock the first thread tries to acquire ◮ lock variations: ◮ reentrant – may be acquired multiple times by the same thread, must be released as many times ◮ read-write – many readers may hold the lock in Java: ◮ every object has its own lock entity ◮ synchronized block is used to acquire/release object lock synchronized ( objectWithLock ) { // acquire the lock // execute code } // release the lock

  10. Critical sections Protect code blocks that require atomic execution (Java): void addElement (Element e) { synchronized (theLock) { // enter critical section size += 1; array[size -1] = e; } // leave critical section: releasing theLock } ◮ change size and array at once, so no other thread interferes Protect read code too to see a consistent state: Element getElement () { synchronized (theLock) { return array[size -1]; } } ◮ do not enter if any other thread is already executing this or the above critical section ◮ forgetting to lock here makes it all meaningless: ◮ general rule: lock at every access !

  11. Java synchronized Synchronized block is a structural way to locking: ◮ secure – protected against forgetting to unlock and exceptions, as in l.acquire (); try { compute (); } finally {l.release (); } ◮ not flexible – may not lock in one method and unlock in another Java specifics: ◮ Java locks are re-entrant but not interruptible ◮ synchronized void methodA() {} is equivalent to void methodA () { synchronized (this) { } } ◮ synchronized static void methodA() {} is equivalent to static void methodA () { synchronized (ThisC.class) { } }

  12. Java ReentrantReadWriteLock It is useful: ◮ split readers and writers, so that readers may execute simultaneously ◮ only single writer is allowed and this implies no readers There are more to locks, e.g. Java read-write lock: ◮ non-fair or fully fair – acquisition order is not specified or by arrival ◮ no reader or writer preference is possible ◮ reentrant ◮ lock downgrading ◮ support interruption during lock acquisition https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadW

  13. Fully synchronized objects ◮ every access to an object is takes the object lock – every method is synchronized ◮ java.util.Collections methods synchronizedMap , synchronizedList Problems, sometimes: ◮ need lock compound operations, e.g. the following may fail if (list.size () >0) v = list.get (0); ◮ do not need to synchronize every access but prefer to lock for a set of operations Solution for the first problem: ◮ client may use additional locking, because we know the object uses the same lock synchronized (list) { if (list.size () >0) v = list.get (0); }

  14. Client side locking ◮ user of an object is required to lock before using the object ◮ more flexible but also error-prone ◮ may forget to take the lock or use different one Java synchronized collections ( Vector , Collections.synchronized* ): ◮ do not provide synchronized looping over the collection ◮ client must synchronize while iterating synchronized (coll) { for (Element e: coll) { // coll.iterator () is used implicitly .. } } ◮ versioned iterators throw ConcurrentModificationException if the collection is changed during iteration

  15. Copy-on-iteration and Copy-on-write Copy-on-iteration: ◮ for each iteration copy the collection while holding the lock ◮ release the lock and iterate over the copy ◮ Advantage : lock is held for a short period ◮ Disadvantage : may be out of date and require lots of copying Copy-on-write: ◮ for each change copy the collection while holding the lock ◮ modify the copy and replace the reference ◮ Advantage : no need to lock or copy for iteration ◮ Disadvantage : may require lots of copying when changed Copy-on-write is often used for listener/observer lists, because they are seldom modified.

  16. Deadlocks Pessimistic locking has one serious danger: ◮ thread 1 takes locks A and B in this order ◮ thread 2 takes locks A and B in the opposite order ◮ they may get into deadlock – no thread may proceed, because waiting for each other resources code A code B ◮ lines signify executing threads

  17. Ordering locks One solution to deadlocks: ◮ order locks by any parameter, e.g. A,B,C ◮ never acquire higher priority locks if already holding lower priority locks ◮ i.e. never try to acquire A if already holding B ◮ may acquire C if already holding B For example: synchronized (list) { Element e = list.get (0); synchronized (e) { // change e } } ◮ never try to acquire list lock while holding an element lock!

  18. Reducing and splitting locks Reducing critical area: ◮ do not hold the lock if not needed: take late, release early ◮ try not to hold the lock for long operations ◮ release and re-acquire if possible Splitting lock: ◮ use 2 or more locks instead of one if possible: ◮ locks protect independent data/logic ◮ just use Java object for a lock lock1 = new Object() ◮ use as in synchronized(lock1) ...

  19. Collection access example This example presents reducing, splitting and ordering locks: ◮ maintain lock for list and separate locks for the elements ◮ order locks, so that list lock is always taken first ◮ for insertion/removal of element take the list lock ◮ when changing the element: 1. take the list lock and get the element 2. take the element lock and release the list lock 3. change the element 4. release the element lock list.lock (); elem = list.get(i); elem.lock (); list.unlock (); elem. someSeriousModification () elem.unlock (); ◮ efficient : each element may be modified in parallel ◮ error-prone : need to ensure correct behavior in case of exceptions

Download Presentation
Download Policy: The content available on the website is offered to you 'AS IS' for your personal information and use only. It cannot be commercialized, licensed, or distributed on other websites without prior consent from the author. To download a presentation, simply click this link. If you encounter any difficulties during the download process, it's possible that the publisher has removed the file from their server.

Recommend


More recommend