470 likes | 603 Vues
Software Transactional Memory. Yoav Cohen Seminar in Distributed Computing Spring 2007. Agenda. Motivation behind STM Intro to STM Static STM Implemented by Shavit and Touitou Dynamic STM Implemented by Herlihy et al. Motivation. Material covered so far: Mutual Exclusion (Netanel)
E N D
Software Transactional Memory Yoav Cohen Seminar in Distributed Computing Spring 2007
Agenda • Motivation behind STM • Intro to STM • Static STM Implemented by Shavit and Touitou • Dynamic STM Implemented by Herlihy et al.
Motivation • Material covered so far: • Mutual Exclusion (Netanel) • Specifications for concurrent objects (Liza) • Registers for concurrent access (Yaniv) • Replace locks with HTM (Royi and Merav) • What else do we need?!
HTM Shortcomings • Blocking - Can still deadlock • A thread is killed without releasing a lock • A thread is interrupted with a Page Fault while holding the lock • It’s hardware • Suggested at 1993 (Herlihy and Moss) - Not yet implemented
STM 101 • Same ideas as HTM • A thread executes a transaction • The transaction either commits or aborts • Different from HTM • Non-blocking - The system makes progress • Implemented in software - Available today
STM 101 • A thread wants to change a shared object • The thread announces it • The thread copies the object to its private memory • The thread changes its private copy • The thread updates the changes
STM 101 • Two ways to update an object • Selective update - Update selected locations Original object Local copy
STM 101 • Two ways to update an object • Replacing the object all together Original object Local copy Original object
Software Transactional MemoryNirShavit, Dan Touitou(1995) A non-blocking concurrency framework implemented in software Uses the selective update approach
Overview Thread 2 Thread 1
The System Model We assume that every shared memory location supports these 4 operations: • Writei(L,v) - thread i writes v to L • Readi(L,v) - thread i reads v from L • LLi(L,v) - thread i reads v from L and marks that L was read by I • SCi(L,v) - thread i writes v to L and returns success if L is marked as read by i. Otherwise it returns failure.
Thread classRec { booleanstable = false; boolean,intstatus= (false,0); //can have two values… booleanallWritten = false; intversion = 0; intsize = 0; intlocs[] = {null}; intoldValues[] = {null}; } Each thread is defined by an instance of a Rec class (short for record). The Rec instance defines the current transaction the thread is executing (only one transaction at a time)
status version size locs[] oldValues[] status version size locs[] oldValues[] status version size locs[] oldValues[] The STM Object This is the shared memory Memory Ownerships Pointers to threads Rec2 Recn Rec1
Flow of a transaction STM Threads release Ownerships startTransaction Thread i Success updateMemory initialize Failure Initiate helping transaction to failed loc (isInitiator:=F) isInitiator? calcNewValues transaction T F acquire Ownerships (Null, 0) (Failure,failed loc) agreeOldValues release Ownerships
The STM Object publicclass STM { intmemory[]; Recownerships[]; publicboolean, int[] startTranscation(Recrec, int[] dataSet){...}; privatevoid initialize(Recrec, int[] dataSet) privatevoid transaction(Recrec, int version, booleanisInitiator) {...}; privatevoidacquireOwnerships(Recrec, int version) {...}; privatevoidreleaseOwnershipd(Recrec, int version) {...}; privatevoidagreeOldValues(Recrec, int version) {...}; privatevoidupdateMemory(Recrec, int version, int[] newvalues) {...}; }
Implementation rec – The thread that executes this transaction. dataSet – The location in memory it needs to own. publicboolean, int[] startTranscation(Recrec, int[] dataSet) { initialize(rec, dataSet); rec.stable = true; transaction(rec, rec.version, true); rec.stable = false; rec.version++; if (rec.status) return (true, rec.oldValues); elsereturnfalse; } This notifies other threads that I can be helped
Implementation rec – The thread that executes this transaction. version – Serial number of the transaction. isInitiator – Am I the initiating thread or the helper? privatevoid transaction(Recrec, int version, booleanisInitiator) { acquireOwnerships(rec, version); // try to own locations (status, failedLoc) = LL(rec.status); if (status == null) { // success in acquireOwnerships if (versoin != rec.version) return; SC(rec.status, (true,0)); } (status, failedLoc) = LL(rec.status); if (status == true) { // execute the transaction agreeOldValues(rec, version); int[] newVals = calcNewVals(rec.oldvalues); updateMemory(rec, version); releaseOwnerships(rec, version); } else { // failed in acquireOwnerships releaseOwnerships(rec, version); if (isInitiator) { RecfailedTrans = ownerships[failedLoc]; if (failedTrans == null) return; else { // execute the transaction that owns the location you want intfailedVer = failedTrans.version; if (failedTrans.stable) transaction(failedTrans, failedVer, false); } } } } Another thread own the locations I need and it hasn’t finished its transaction yet. So I go out and execute its transaction in order to help it.
Implementation privatevoidacquireOwnerships(Recrec, int version) { for (int j=1; j<=rec.size; j++) { while (true) do { int loc = locs[j]; if LL(rec.status) != nullreturn; // transaction completed by some other thread Rec owner = LL(ownerships[loc]); if (rec.version != version) return; if(owner == rec) break; // location is already mine if (owner == null) { // acquire location if ( SC(rec.status, (null, 0)) ) { if ( SC(ownerships[loc], rec) ) { break; } } } else {// location is taken by someone else if ( SC(rec.status, (false, j)) ) return; } } } } If I’m not the last one to read this field, it means that another thread is trying to execute this transaction. Try to loop until I succeed or until the other thread completes the transaction
Implementation Copy the dataSet to my private space privatevoidagreeOldValues(Recrec, int version) { for (int j=1; j<=rec.size; j++) { int loc = locs[j]; if ( LL(rec.oldvalues[loc]) != null ) { if (rec.version != version) return; SC(rec.oldvalues[loc], memory[loc]); } } } privatevoidupdateMemory(Recrec, int version, int[] newvalues) { for (int j=1; j<=rec.size; j++) { int loc = locs[j]; intoldValue = LL(memory[loc]); if (rec.allWritten) return; // work is done if (rec.version != version) return; if (oldValue != newValues[j]) SC(memory[loc], newValues[j]); } if (! LL(rec.allWritten) ) { if (rec.version != version) SC(rec.allWritten, true); } } Selectively update the shared memory
Proving the Non-blocking Property • Every failing transaction has a thread which writes Failure to its status field. • Intuition – Let’s show that a situation where the system is stuck can’t happen. • Proof outline – Assume the system is stuck and derive a contradiction.
Proving the Non-blocking Property • Claim (No proof): Given a failing transaction, in which the failing thread failed to acquire a location A, all threads executing it will never acquire ownership of a location which is higher than the A.
Proving the Non-blocking Property • The system is stuck – There are infinitely many transactions that do not succeed. • Number of failing transactions is finite – The other ones are stuck in a loop • Number of failing transactions is infinite
Proving the Non-blocking Property • Number of failing transactions is finite • Other ones are stuck in the loop in acquireOwnerships. • This can only happen if some threads are trying to acquire the same location for the same transaction. • This state can’t be reached (on the board). • A contradiction. T1 (Loop) T2 (Fail) T3 (Loop) … … Tn (Loop)
Proving the Non-blocking Property • Number of failing transactions is infinite • Since the number of locations is finite, there exists at least one location which is a failing location infinitely often. • Choose A, the highest of those locations. • Intuition – If A is a failing location infinitely often, there are infinitely many transactions who acquired A and failed. • Contradiction – A is not the highest location.
Limitations of STM • Static - Information about transactions is required beforehand: • Size • DataSet • A static software transactional memory is limited only to predefined transactions and data structures.
Performance of STM • In stable scenarios, it has a lower throughput than locks. • But, it is non-blocking, hence the system will always progress.
Dynamic STMHerlihy, Moir, Luchangco, Scherer (2003) Suited for Dynamic-Sized Data Structures Uses the pointers swap technique Introduces a weaker non-blocking property called obstruction freedom Introduces Contention Managers
Thread • A thread announces the start of a transaction • A thread executes a series of operations on shared objects • A thread tries to commit the transaction publicclass TMThread { publicvoid beginTransaction(); publicvoid abortTransaction(); publicboolean commitTransaction(); }
Flow of a transaction Concurrent Dynamic-Sized Data Structure Threads someMethod() Thread.startTransaction() SharedObject.open(WRITE) … … SharedObject.release() Thread.commitTransaction() end someMethod() TMThread i Shared Object Committing a transaction is done atomically!
A concurrent systemView from above Threads TMThread i TMThread j TMThread k TMThread m
Opening a shared object Intuition: • When you open a shared object you get a clone. • You change the clone • When you commit the transaction the clone replaces the original object
Locator transaction oldObject newObject Structure of a shared object TMObject Transaction ACTIVE Data Data
Implementation class Transaction { enum Status {ACTIVE, COMMITTED, ABORTED}; Status status = Status.ACTIVE; } Initialized to ACTIVE
Implementation classTMObject { // internal classes enumAccessType {WRITE, READ}; class Locator { Transaction transaction; Object newObject; Object oldObject; } // data members Locator locator; // methods publicvoid open(AccessType type) throwsDeniedException {}; } Ways to access the object Pointers to the object’s data Access the object
Opening a shared object for Writing public Object open(AccessType type) throwsDeniedException { if (type==AccessType.WRITE) { Locator newLocator = new Locator(); newLocator.transaction = TMThread.getCurrentTransaction(); if (locator.transaction.status==Status.ACTIVE) { resolveConflict(); } else { if (locator.transaction.status==Status.COMMITTED) { newLocator.oldObject = locator.newObject; newLocator.newObject = locator.newObject.clone(); } elseif (locator.transaction.status==Status.ABORTED) { newLocator.oldObject = locator.oldObject; newLocator.newObject = locator.oldObject.clone(); } validateTransaction(newLocator.transaction) returnnewLocator.newObject; } } } Trying to access an already open object Make sure the transaction is still active and its read table is up to date
locator transaction oldObject newObject newLocator transaction oldObject newObject Opening a shared object for Writing COMMITTED Data Data ACTIVE Because the last transaction committed we take its changes Data clone()
locator transaction oldObject newObject newLocator transaction oldObject newObject Opening a shared object for Writing ABORTED Data Data ACTIVE Because the last transaction aborted we discard its changes Data clone()
Opening a shared object for Reading • Intuition: Just have to make sure threads read the most updated version • Practice: • We keep a Thread Local table of the objects we opened for read and their latest version • We keep a counter for each object to track number of open and release invocations • We increment the counter when open is called • We decrement the counter when release is called. If counter == 0 we remove the object from the table
Opening a shared object for Reading classReadTable { classReadTableItem { Object object; intcounter; } Map<Integer, ReadTableItem> readTable; void insert(intobjID, Object objInst) { if ( readTable.containsKey(objID) ) { ReadTableItem item = readTable.get(objID); item.counter++; } else { ReadTableItemnewItem = newReadTableItem(objInst,1); readTable.put(objID, newItem); } } void remove(intobjID) { ... } } The object and its counter This is how we open an object for reading.
Opening a shared object for Reading publicclassTMThread { ThreadLocal<ReadTable> readTable; publicvoidbeginTransaction(); publicvoidabortTransaction(); publicbooleancommitTransaction(); } Each thread has a read table
Committing a transaction Commiting a transaction requires 2 steps: • Validating the read table of the thread • Using Compare&Swap to change the transaction status from ACTIVE to COMMITED
Example publicclass SomeDynamicSizedDataStructure { TMObject data; publicboolean insert(Element elem) { TMThread thread = (TMThread) Thread.getCurrentThread(); while (true) { // loop until commited or aborted thread.beginTransaction(); boolean result; try { data.open(WRITE); /* Insert elem to data here */ data.release(); result = true; } catch (DeniedException e) { /* Could not open a shared object */ result = fasle; } if (thread.commitTransaction() == true) return result; } } }
Non-blocking • The DSTM implementation ensures a non-blocking property called obstruction-freedom. • It means that any thread that runs alone for a long enough time makes progress. • Weaker than lockout-freedom of STM
Contention Management • Contention Management policy - What does a thread do when it encounters a conflict? • The DSTM implementation has an extension mechanism to allow for different contention management policies. • These extensions are called Contention Managers.
Contention Managers • Each thread has a reference to a Contention Manager. • Whenever the thread encounters a conflict, it advices with its Contention Manger to decide what to do. • When a conflict is encountered a thread can either abort itself, wait or abort the other transaction.
Summary • Pros and Cons of STM • Pros and Cons of DSTM • Discussion – what should the future concurrency framework look like?