123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2012 GarageGames, LLC
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to
- // deal in the Software without restriction, including without limitation the
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- // sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- // IN THE SOFTWARE.
- //-----------------------------------------------------------------------------
- #ifndef _THREADSAFEDEQUE_H_
- #define _THREADSAFEDEQUE_H_
- #ifndef _THREADSAFEFREELIST_H_
- # include "platform/threads/threadSafeFreeList.h"
- #endif
- #include "platform/tmm_off.h"
- /// Fast, lock-free double-ended queue for concurrent access.
- ///
- /// @param T Type of list elements; must have default contructor.
- template< typename T >
- class ThreadSafeDeque
- {
- // Lock-free deques using just single-word atomic writes are
- // very tricky as each pointer update must immediately result
- // in a fully valid list state. The idea here is to maintain the
- // deque's head and tail pointers unreliably but otherwise keep a
- // regular double-linked list (since we never insert nodes in the
- // middle, single-word writes are all we need).
- //
- // Deletions are a bit less straightforward and require the threads
- // to work cooperatively. Since failure of a pointer update depends
- // on the deletion state, the deletion flag has to be encoded into
- // the link fields. However, as there are two link fields this creates
- // two independent deletion flags for each single node, one on the
- // next link and one on the prev link.
- //
- // This will not lead to a problem, though, as it only becomes relevant
- // when there is only a single value in the list which, even if the
- // respective node gets both deleted and appended/prepended a new node,
- // will result in a valid list state.
- public:
- typedef T ValueType;
- protected:
- class Node;
- class DeleteNode;
- typedef ThreadSafeRef< Node > NodeRef;
- /// List node.
- class Node : public ThreadSafeFreeListNode< Node, DeleteNode >
- {
- public:
- friend class DeleteNode; // mFreeList;
- typedef typename ThreadSafeDeque< T >::ValueType ValueType;
- /// Thread claim flag. This is to prevent two threads who concurrently
- /// do a tryPopFront() and tryPopBack() respectively on a deque with just
- /// a single node to both claim and return the same value (which would happen
- /// without the flag as otherwise both threads would use two different
- /// deletion bits for claiming the node).
- U32 mIsClaimed;
- /// Link to the freelist that the node has been
- /// allocated from.
- ThreadSafeFreeList< Node >& mFreeList;
- /// Value contained in the node.
- ValueType mValue;
- /// Reference to next node and deletion bit.
- NodeRef mNext;
- /// Reference to previous node and deletion bit.
- NodeRef mPrev;
- /// Construct an unlinked node allocated from "freeList".
- Node( ThreadSafeFreeList< Node >& freeList, const ValueType& value )
- : mIsClaimed( 0 ), mFreeList( freeList ), mValue( value ) {}
- };
- class DeleteNode
- {
- public:
- template< typename N >
- static void destroy( N* ptr )
- {
- AssertFatal( ptr->mIsClaimed,
- "ThreadSafeDeque::DeleteNode::destroy() - deleting unclaimed node" );
- destructInPlace( ptr );
- ptr->mFreeList.free( ptr );
- }
- };
- #ifdef TORQUE_DEBUG
- S32 mNumValues;
- #endif
- /// Reference to the head node.
- NodeRef mHead;
- ///
- NodeRef mTail;
- /// Free list for list nodes.
- ThreadSafeFreeList< Node > mFreeList;
- /// @return the leftmost node in the list.
- /// @note Updates the list state and may purge deleted nodes.
- NodeRef getHead();
- /// @return the rightmost node in the list.
- /// @note Updates the list state and may purge deleted nodes.
- NodeRef getTail();
- public:
- /// Construct an empty deque.
- ThreadSafeDeque()
- {
- #ifdef TORQUE_DEBUG
- mNumValues = 0;
- #endif
- }
- ~ThreadSafeDeque()
- {
- ValueType value;
- while( tryPopFront( value ) );
- AssertFatal( isEmpty(), "ThreadSafeDeque::~ThreadSafeDeque() - not empty" );
- }
- /// @return true if the queue is empty.
- bool isEmpty()
- {
- return ( !getHead() && !getTail() );
- }
- /// Prepend the given value to the list.
- void pushFront( const ValueType& value );
- /// Append the given value to the list.
- void pushBack( const ValueType& value );
- /// Try to take the leftmost value from the deque.
- /// Fails if the deque is empty at the time the method tries to
- /// take a node from the list.
- bool tryPopFront( ValueType& outValue );
- /// Try to take the rightmost value from the deque.
- /// Fails if the deque is empty at the time the method tries to
- /// take a node from the list.
- bool tryPopBack( ValueType& outValue );
- void dumpDebug()
- {
- #ifdef TORQUE_DEBUG
- Platform::outputDebugString( "[ThreadSafeDeque] numValues=%i", mNumValues );
- mFreeList.dumpDebug();
- #endif
- }
- };
- // The getHead() and getTail() code here is pretty much brute-force in order
- // to keep the complexities of synchronizing it bounded. We just let each
- // thread work as if it is the only thread but require each one to start from
- // scratch on each iteration.
- template< typename T >
- typename ThreadSafeDeque< T >::NodeRef ThreadSafeDeque< T >::getHead()
- {
- // Find leftmost node.
- NodeRef result;
- while( 1 )
- {
- // Iterate through to leftmost node.
-
- {
- NodeRef head = mHead;
- while( head != NULL )
- {
- NodeRef prev = head->mPrev;
- if( prev != NULL )
- mHead.trySetFromTo( head, prev, NodeRef::TAG_Unset );
- else
- break;
-
- head = mHead;
- }
- }
-
- // Clear out dead nodes at front of list.
-
- {
- NodeRef head = mHead;
- if( head && head->mPrev.isTagged() )
- {
- NodeRef next = head->mNext;
-
- mHead.trySetFromTo( head, next, NodeRef::TAG_Unset );
- mTail.trySetFromTo( head, next, NodeRef::TAG_Unset );
- if( next != NULL )
- next->mPrev.trySetFromTo( head, NULL );
- head->mNext.trySetFromTo( next, NULL, NodeRef::TAG_Set );
-
- continue; // Restart.
- }
- }
-
- // Try head.
-
- NodeRef head = mHead;
- if( head != NULL && !head->mPrev.isTagged() )
- {
- result = head;
- break;
- }
-
- // Try tail.
-
- if( !head )
- {
- head = mTail;
- if( !head )
- break;
- }
- // Update head.
-
- NodeRef prev = head->mPrev;
- if( head->mPrev != NULL )
- {
- if( !mHead.trySetFromTo( head, prev, NodeRef::TAG_Unset ) )
- mHead.trySetFromTo( NULL, prev );
- }
- else
- mHead.trySetFromTo( NULL, head );
- }
- AssertFatal( !result.isTagged(), "ThreadSafeDeque::getHead() - head got tagged" );
- return result;
- }
- template< typename T >
- typename ThreadSafeDeque< T >::NodeRef ThreadSafeDeque< T >::getTail()
- {
- // Find rightmost node.
- NodeRef result;
- while( 1 )
- {
- // Iterate through to rightmost node.
-
- {
- NodeRef tail = mTail;
- while( tail != NULL )
- {
- NodeRef next = tail->mNext;
- if( next != NULL )
- mTail.trySetFromTo( tail, next, NodeRef::TAG_Unset );
- else
- break;
-
- tail = mTail;
- }
- }
-
- // Clear out dead nodes at tail of list.
-
- {
- NodeRef tail = mTail;
- if( tail != NULL && tail->mNext.isTagged() )
- {
- NodeRef prev = tail->mPrev;
-
- mHead.trySetFromTo( tail, prev, NodeRef::TAG_Unset );
- mTail.trySetFromTo( tail, prev, NodeRef::TAG_Unset );
- if( prev != NULL )
- prev->mNext.trySetFromTo( tail, NULL );
- tail->mPrev.trySetFromTo( prev, NULL, NodeRef::TAG_Set );
-
- continue; // Restart.
- }
- }
-
- // Try tail.
-
- NodeRef tail = mTail;
- if( tail != NULL && !tail->mNext.isTagged() )
- {
- result = tail;
- break;
- }
-
- // Try head.
-
- if( !tail )
- {
- tail = mHead;
- if( !tail )
- break;
- }
- // Update tail.
-
- NodeRef next = tail->mNext;
- if( next != NULL )
- {
- if( !mTail.trySetFromTo( tail, next, NodeRef::TAG_Unset ) )
- mTail.trySetFromTo( NULL, next );
- }
- else
- mTail.trySetFromTo( NULL, tail );
- }
- AssertFatal( !result.isTagged(), "ThreadSafeDeque::getTail() - tail got tagged" );
- return result;
- }
- template< typename T >
- void ThreadSafeDeque< T >::pushFront( const ValueType& value )
- {
- NodeRef nextNode;
- NodeRef newNode;
- NodeRef::unsafeWrite( newNode, new ( mFreeList ) Node( mFreeList, value ) );
- while( 1 )
- {
- nextNode = getHead();
- if( !nextNode )
- {
- newNode->mNext = NULL;
- if( mHead.trySetFromTo( NULL, newNode ) )
- break;
- }
- else
- {
- newNode->mNext = nextNode;
- if( nextNode->mPrev.trySetFromTo( NULL, newNode, NodeRef::TAG_FailIfSet ) )
- break;
- }
- }
- #ifdef TORQUE_DEBUG
- dFetchAndAdd( mNumValues, 1 );
- #endif
- }
- template< typename T >
- void ThreadSafeDeque< T >::pushBack( const ValueType& value )
- {
- NodeRef prevNode;
- NodeRef newNode;
- NodeRef::unsafeWrite( newNode, new ( mFreeList ) Node( mFreeList, value ) );
- while( 1 )
- {
- prevNode = getTail();
- if( !prevNode )
- {
- newNode->mPrev = NULL;
- if( mHead.trySetFromTo( NULL, newNode ) ) // use head so we synchronize with pushFront
- break;
- }
- else
- {
- newNode->mPrev = prevNode;
- if( prevNode->mNext.trySetFromTo( NULL, newNode, NodeRef::TAG_FailIfSet ) )
- break;
- }
- }
- #ifdef TORQUE_DEBUG
- dFetchAndAdd( mNumValues, 1 );
- #endif
- }
- template< typename T >
- bool ThreadSafeDeque< T >::tryPopFront( ValueType& outValue )
- {
- NodeRef oldHead;
- while( 1 )
- {
- oldHead = getHead();
- if( !oldHead )
- return false;
- // Try to claim the node.
- if( oldHead->mPrev.trySetFromTo( NULL, NULL, NodeRef::TAG_SetOrFail ) )
- {
- if( dCompareAndSwap( oldHead->mIsClaimed, 0, 1 ) )
- break;
- else
- continue;
- }
- }
- outValue = oldHead->mValue;
- oldHead = NULL;
- // Cleanup.
- getHead();
- #ifdef TORQUE_DEBUG
- dFetchAndAdd( mNumValues, -1 );
- #endif
- return true;
- }
- template< typename T >
- bool ThreadSafeDeque< T >::tryPopBack( ValueType& outValue )
- {
- NodeRef oldTail;
- while( 1 )
- {
- oldTail = getTail();
- if( !oldTail )
- return false;
- // Try to claim the node.
- if( oldTail->mNext.trySetFromTo( NULL, NULL, NodeRef::TAG_SetOrFail ) )
- {
- if( dCompareAndSwap( oldTail->mIsClaimed, 0, 1 ) )
- break;
- }
- }
- outValue = oldTail->mValue;
- oldTail = NULL;
- // Cleanup.
- getTail();
- #ifdef TORQUE_DEBUG
- dFetchAndAdd( mNumValues, -1 );
- #endif
- return true;
- }
- #include "platform/tmm_on.h"
- #endif // _THREADSAFEDEQUE_H_
|