X X X - Old X - New Op (X - Old) Interlockedcompareexchange X X - Old
X X X - Old X - New Op (X - Old) Interlockedcompareexchange X X - Old
X X X - Old X - New Op (X - Old) Interlockedcompareexchange X X - Old
The defining characteristic of a non-blocking algorithm is that stopping a thread does not prevent the rest of the system from making progress. There are different non-blocking guarantees:
(i)Obstruction freedom: A thread makes progress as long as there is no contention but live lock is possible. !"ponential backoff can be used to work around live lock. (ii)#ock freedom.: The system as a whole makes progress. (iii)$ait freedom: !very thread makes progress even when faced with contention. %ery few non-blocking algorithms achieve this.
&on-blocking algorithms are immune from lock contention priority inversion and convoying. &on-blocking algorithms have a lot of advantages but with these come a new set of problems that need to be understood. &on-blocking algorithms are based on atomic operations such as the methods of the Interlocked class. A few nonblocking algorithms are simple. 'ost are comple" because the algorithms must handle all possible interleaving of instruction streams from contending processors. A trivial non-blocking algorithm is counting via an interlocked increment instead of a lock. The interlocked instruction avoids lock overhead and pathologies.
*n the wrong code if the count was originally + two threads e"ecuting the wrong code might both decrement the count and then both see it as ,ero at the same time. The correct code performs the decrement and test as a single atomic operation. 'ost non-blocking algorithms involve a loop that attempts to perform an action using one or more compare-and-swap (-A.) operations and retries when one of the -A. operations fails. A simple and useful e"ample is implementing a thread-safe fetch-and-op. A fetch-and-op reads a value from a location computes a new value from it and stores the new value. *n e"ample both a locked version and a nonblocking version that operate on a location x. The non-blocking version reads location x into a local temporary x_old and computes a new value x_new = op(x_old). The routine InterlockedCompareExchange stores the new value unless x is now different than x_old.
-omparison of #ocked and #ockless -ode for /etch-and-op void LockedFetchAndOp( long& x ) ac!"ire lock x = op(x)# relea$e lock % void &on'lockingFetchAndOp( volatile long& x ) long x_old( x_new( x_wa$# do x_old = x# x_new = op(x_old)# x_wa$ = InterlockedCompareExchange(&x( x_new( x_old)# % while( x_wa$)=x_old )# % ABA PROBLEM: *n -omparison of #ocked and #ockless -ode for /etch-and-op there is a time interval
This e"ample shows the wrong way and right way to decrement and test of the reference count is p-(ref)count.
between when a thread e"ecutesx_old = xand when the thread e"ecutes InterlockedCompareExchange. 0uring this interval other processors might perform other fetchand-op operations. /or e"ample suppose the initial value read is A. An intervening se1uence of fetch-and-op operations by other processorsmight change " to 2 and then back to A. $hen the original thread e"ecutes InterlockedCompareExchange it will be as if the other processor3s actions never happened. As long as the order in which op ise"ecuted does not matter there is no problem. The net result is the same as if the fetch-and-op operations were reordered such that the intervening se1uence happens before the first read. 2ut sometimes fetch-and-op has uses where changing " from A to 2 to A does make a difference. The problem is indeed known as the A2A problem.
#ockless *mplementation of a #inked .tack that 'ay .uffer from A2A 4roblem &ode* +op# ,, -ointer to top item on $tack. void 'rokenLockle$$-"$h( &ode* node ) Item *t_old( t_wa$# do Item* t_old = +op# n/0next = t_old# t_wa$ = InterlockedCompareExchange(&+op(node(t_old)# % while( t_wa$)=t_old )# % Item* 'rokenLockle$$-op() Item *t_old( *t_wa$( *t_new# do t_old = +op# t_new = t_old/0next# ,, A'A pro1lem ma2 $trike 1elow) t_wa$ = InterlockedCompareExchange(&+op(t_new(t_old)# % while( t_wa$)=t_old )# ret"rn t_old# %