29 parallel programming iii
play

29. Parallel Programming III public: ... void withdraw(int amount) - PowerPoint PPT Presentation

Deadlock Motivation class BankAccount { int balance = 0; std::recursive_mutex m; using guard = std::lock_guard<std::recursive_mutex>; 29. Parallel Programming III public: ... void withdraw(int amount) { guard g(m); ... } void


  1. Deadlock Motivation class BankAccount { int balance = 0; std::recursive_mutex m; using guard = std::lock_guard<std::recursive_mutex>; 29. Parallel Programming III public: ... void withdraw(int amount) { guard g(m); ... } void deposit(int amount){ guard g(m); ... } Deadlock and Starvation Producer-Consumer, The concept of the monitor, Condition Variables [Deadlocks : Williams, Kap. 3.2.4-3.2.5] void transfer(int amount, BankAccount& to){ [Condition Variables: Williams, Kap. 4.1] guard g(m); withdraw(amount); Problem? to.deposit(amount); } }; 975 976 Deadlock Motivation Deadlock Suppose BankAccount instances x and y Thread 1: x.transfer(1,y); Thread 2: y.transfer(1,x); Deadlock: two or more processes are acquire lock for x mutually blocked because each process waits for another of these processes to acquire lock for y withdraw from x proceed. acquire lock for y withdraw from y acquire lock for x 977 978

  2. Threads and Resources Deadlock – Detection A deadlock for threads t 1 , . . . , t n occurs when the graph describing the relation of the n threads and resources r 1 , . . . , r m contains a cycle. Grafically t and Resources (Locks) r wants r 1 t 2 t 4 a Thread t attempts to acquire resource a : t Resource b is held by thread q : s b r 2 t 1 r 4 r 3 t 3 held by 979 980 Techniques Back to the Example class BankAccount { Deadlock detection detects cycles in the dependency graph. int id; // account number, also used for locking order std::recursive_mutex m; ... Deadlocks can in general not be healed: releasing locks generally public: leads to inconsistent state ... Deadlock avoidance amounts to techniques to ensure a cycle can void transfer(int amount, BankAccount& to){ never arise if (id < to.id){ guard g(m); guard h(to.m); Coarser granularity “one lock for all” withdraw(amount); to.deposit(amount); Two-phase locking with retry mechanism } else { Lock Hierarchies guard g(to.m); guard h(m); ... withdraw(amount); to.deposit(amount); Resource Ordering } } }; 981 982

  3. C++11 Style By the way... class BankAccount { class BankAccount { int balance = 0; ... std::recursive_mutex m; std::recursive_mutex m; using guard = std::lock_guard<std::recursive_mutex>; using guard = std::lock_guard<std::recursive_mutex>; public: public: ... ... void withdraw(int amount) { guard g(m); ... } void transfer(int amount, BankAccount& to){ void deposit(int amount){ guard g(m); ... } std::lock(m,to.m); // lock order done by C++ // tell the guards that the lock is already taken: void transfer(int amount, BankAccount& to){ guard g(m,std::adopt_lock); guard h(to.m,std::adopt_lock); withdraw(amount); withdraw(amount); This would have worked here also. to.deposit(amount); to.deposit(amount); But then for a very short amount of } } time, money disappears, which does }; }; not seem acceptable (transient incon- 983 984 sistency!) Starvation und Livelock Politelock Starvation: the repeated but unsuccess- ful attempt to acquire a resource that was recently (transiently) free. Livelock: competing processes are able to detect a potential deadlock but make no progress while trying to resolve it. 985 986

  4. Producer-Consumer Problem Sequential implementation (unbounded buffer) class BufferS { std::queue<int> buf; public: Two (or more) processes, producers and consumers of data should void put(int x){ become decoupled by some data structure. not thread-safe buf.push(x); } Fundamental Data structure for building pipelines in software. int get(){ t 1 t 2 while (buf.empty()){} // wait until data arrive int x = buf.front(); buf.pop(); return x; } }; 987 988 How about this? Well, then this? class Buffer { void put(int x){ std::recursive_mutex m; guard g(m); using guard = std::lock_guard<std::recursive_mutex>; buf.push(x); std::queue<int> buf; } public: int get(){ void put(int x){ guard g(m); m.lock(); buf.push(x); while (buf.empty()){ Ok this works, but it wastes CPU } m.unlock(); Deadlock time. int get(){ guard g(m); m.lock(); while (buf.empty()){} } int x = buf.front(); int x = buf.front(); buf.pop(); buf.pop(); return x; m.unlock(); } return x; }; } 989 990

  5. Better? Moral void put(int x){ guard g(m); buf.push(x); } int get(){ We do not want to implement waiting on a condition ourselves. m.lock(); Ok a little bit better, limits reactiv- There already is a mechanism for this: condition variables . while (buf.empty()){ ity though. m.unlock(); The underlying concept is called Monitor . std::this_thread::sleep_for(std::chrono::milliseconds(10)); m.lock(); } int x = buf.front(); buf.pop(); m.unlock(); return x; } 991 992 Monitor Monitors vs. Locks Monitor abstract data structure equipped with a set of operations that run in mutual exclusion and that can be synchronized. Invented by C.A.R. Hoare and Per Brinch Hansen (cf. Monitors – An Operating Sys- Per Brinch Hansen C.A.R. Hoare, (1938-2007) *1934 tem Structuring Concept, C.A.R. Hoare 1974) 993 994

  6. Monitor and Conditions Condition Variables #include <mutex> Monitors provide, in addition to mutual exclusion, the following #include <condition_variable> mechanism: ... Waiting on conditions: If a condition does not hold, then class Buffer { std::queue<int> buf; Release the monitor lock Wait for the condition to become true std::mutex m; // need unique_lock guard for conditions Check the condition when a signal is raised using guard = std::unique_lock<std::mutex>; Signalling: Thread that might make the condition true: std::condition_variable cond; public: Send signal to potentially waiting threads ... }; 995 996 Condition Variables Technical Details class Buffer { ... A thread that waits using cond.wait runs at most for a short time public: void put(int x){ on a core. After that it does not utilize compute power and guard g(m); “sleeps”. buf.push(x); The notify (or signal-) mechanism wakes up sleeping threads that cond.notify_one(); subsequently check their conditions. } int get(){ cond.notify_one signals one waiting thread guard g(m); cond.notify_all signals all waiting threads. Required when waiting cond.wait(g, [&]{return !buf.empty();}); thrads wait potentially on different conditions. int x = buf.front(); buf.pop(); return x; } }; 997 998

  7. Technical Details By the way, using a bounded buffer.. class Buffer { Java Example ... CircularBuffer<int,128> buf; // from lecture 6 synchronized long get() { long x; public: Many other programming langauges while (isEmpty()) void put(int x){ guard g(m); try { offer the same kind of mechanism. cond.wait(g, [&]{return !buf.full();}); wait (); } catch (InterruptedException e) { } The checking of conditions (in a loop!) buf.put(x); x = doGet(); cond.notify_all(); return x; has to be usually implemented by the } } programmer. int get(){ guard g(m); synchronized put(long x){ doPut(x); cond.wait(g, [&]{return !buf.empty();}); notify (); cond.notify_all(); } return buf.get(); } }; 999 1000

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