mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-16 09:04:38 +00:00
Merge pull request #1346 from eightyeight/fix-threadpool-tests
This commit is contained in:
commit
31afbedfb6
4 changed files with 100 additions and 4 deletions
|
|
@ -44,6 +44,20 @@ public:
|
||||||
mResults[mIndex] = mIndex;
|
mResults[mIndex] = mIndex;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A worker that delays for some time. We'll use this to test the ThreadPool's
|
||||||
|
// synchronous and asynchronous operations.
|
||||||
|
struct DelayItem : public ThreadPool::WorkItem
|
||||||
|
{
|
||||||
|
U32 ms;
|
||||||
|
DelayItem(U32 _ms) : ms(_ms) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void execute()
|
||||||
|
{
|
||||||
|
Platform::sleep(ms);
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_FIX(ThreadPool, BasicAPI)
|
TEST_FIX(ThreadPool, BasicAPI)
|
||||||
|
|
@ -63,8 +77,7 @@ TEST_FIX(ThreadPool, BasicAPI)
|
||||||
pool->queueWorkItem(item);
|
pool->queueWorkItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all items to complete.
|
pool->waitForAllItems();
|
||||||
pool->flushWorkItems();
|
|
||||||
|
|
||||||
// Verify.
|
// Verify.
|
||||||
for (U32 i = 0; i < numItems; i++)
|
for (U32 i = 0; i < numItems; i++)
|
||||||
|
|
@ -72,4 +85,37 @@ TEST_FIX(ThreadPool, BasicAPI)
|
||||||
results.clear();
|
results.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_FIX(ThreadPool, Asynchronous)
|
||||||
|
{
|
||||||
|
const U32 delay = 500; //ms
|
||||||
|
|
||||||
|
// Launch a single delaying work item.
|
||||||
|
ThreadPool* pool = &ThreadPool::GLOBAL();
|
||||||
|
ThreadSafeRef<DelayItem> item(new DelayItem(delay));
|
||||||
|
pool->queueWorkItem(item);
|
||||||
|
|
||||||
|
// The thread should not yet be finished.
|
||||||
|
EXPECT_EQ(false, item->hasExecuted());
|
||||||
|
|
||||||
|
// Wait til the item should have completed.
|
||||||
|
Platform::sleep(delay * 2);
|
||||||
|
|
||||||
|
EXPECT_EQ(true, item->hasExecuted());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_FIX(ThreadPool, Synchronous)
|
||||||
|
{
|
||||||
|
const U32 delay = 500; //ms
|
||||||
|
|
||||||
|
// Launch a single delaying work item.
|
||||||
|
ThreadPool* pool = &ThreadPool::GLOBAL();
|
||||||
|
ThreadSafeRef<DelayItem> item(new DelayItem(delay));
|
||||||
|
pool->queueWorkItem(item);
|
||||||
|
|
||||||
|
// Wait for the item to complete.
|
||||||
|
pool->waitForAllItems();
|
||||||
|
|
||||||
|
EXPECT_EQ(true, item->hasExecuted());
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -120,6 +120,7 @@ void ThreadPool::Context::updateAccumulatedPriorityBiases()
|
||||||
void ThreadPool::WorkItem::process()
|
void ThreadPool::WorkItem::process()
|
||||||
{
|
{
|
||||||
execute();
|
execute();
|
||||||
|
mExecuted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|
@ -281,6 +282,8 @@ void ThreadPool::WorkerThread::run( void* arg )
|
||||||
Platform::outputDebugString( "[ThreadPool::WorkerThread] thread '%i' takes item '0x%x'", getId(), *workItem );
|
Platform::outputDebugString( "[ThreadPool::WorkerThread] thread '%i' takes item '0x%x'", getId(), *workItem );
|
||||||
#endif
|
#endif
|
||||||
workItem->process();
|
workItem->process();
|
||||||
|
|
||||||
|
dFetchAndAdd( mPool->mNumPendingItems, ( U32 ) -1 );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
waitForSignal = true;
|
waitForSignal = true;
|
||||||
|
|
@ -318,6 +321,7 @@ ThreadPool::ThreadPool( const char* name, U32 numThreads )
|
||||||
: mName( name ),
|
: mName( name ),
|
||||||
mNumThreads( numThreads ),
|
mNumThreads( numThreads ),
|
||||||
mNumThreadsAwake( 0 ),
|
mNumThreadsAwake( 0 ),
|
||||||
|
mNumPendingItems( 0 ),
|
||||||
mThreads( 0 ),
|
mThreads( 0 ),
|
||||||
mSemaphore( 0 )
|
mSemaphore( 0 )
|
||||||
{
|
{
|
||||||
|
|
@ -409,7 +413,7 @@ void ThreadPool::queueWorkItem( WorkItem* item )
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Put the item in the queue.
|
// Put the item in the queue.
|
||||||
|
dFetchAndAdd( mNumPendingItems, 1 );
|
||||||
mWorkItemQueue.insert( item->getPriority(), item );
|
mWorkItemQueue.insert( item->getPriority(), item );
|
||||||
|
|
||||||
mSemaphore.release();
|
mSemaphore.release();
|
||||||
|
|
@ -440,6 +444,26 @@ void ThreadPool::flushWorkItems( S32 timeOut )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ThreadPool::waitForAllItems( S32 timeOut )
|
||||||
|
{
|
||||||
|
U32 endTime = 0;
|
||||||
|
if( timeOut != -1 )
|
||||||
|
endTime = Platform::getRealMilliseconds() + timeOut;
|
||||||
|
|
||||||
|
// Spinlock until there are no items that have not been processed.
|
||||||
|
|
||||||
|
while( dAtomicRead( mNumPendingItems ) )
|
||||||
|
{
|
||||||
|
Platform::sleep( 25 );
|
||||||
|
|
||||||
|
// Stop if we have exceeded our processing time budget.
|
||||||
|
|
||||||
|
if( timeOut != -1
|
||||||
|
&& Platform::getRealMilliseconds() >= endTime )
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
void ThreadPool::queueWorkItemOnMainThread( WorkItem* item )
|
void ThreadPool::queueWorkItemOnMainThread( WorkItem* item )
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,9 @@ class ThreadPool
|
||||||
/// This is the primary function to implement by subclasses.
|
/// This is the primary function to implement by subclasses.
|
||||||
virtual void execute() = 0;
|
virtual void execute() = 0;
|
||||||
|
|
||||||
|
/// This flag is set after the execute() method has completed.
|
||||||
|
bool mExecuted;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/// Construct a new work item.
|
/// Construct a new work item.
|
||||||
|
|
@ -201,7 +204,8 @@ class ThreadPool
|
||||||
/// @param context The work context in which the item should be placed.
|
/// @param context The work context in which the item should be placed.
|
||||||
/// If NULL, the root context will be used.
|
/// If NULL, the root context will be used.
|
||||||
WorkItem( Context* context = 0 )
|
WorkItem( Context* context = 0 )
|
||||||
: mContext( context ? context : Context::ROOT_CONTEXT() )
|
: mContext( context ? context : Context::ROOT_CONTEXT() ),
|
||||||
|
mExecuted( false )
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,6 +233,12 @@ class ThreadPool
|
||||||
/// Return the item's base priority value.
|
/// Return the item's base priority value.
|
||||||
/// @return item priority; defaults to 1.0.
|
/// @return item priority; defaults to 1.0.
|
||||||
virtual F32 getPriority();
|
virtual F32 getPriority();
|
||||||
|
|
||||||
|
/// Has this work item been executed already?
|
||||||
|
bool hasExecuted() const
|
||||||
|
{
|
||||||
|
return mExecuted;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef ThreadSafeRef< WorkItem > WorkItemPtr;
|
typedef ThreadSafeRef< WorkItem > WorkItemPtr;
|
||||||
|
|
@ -255,6 +265,9 @@ class ThreadPool
|
||||||
/// Number of worker threads guaranteed to be non-blocking.
|
/// Number of worker threads guaranteed to be non-blocking.
|
||||||
U32 mNumThreadsReady;
|
U32 mNumThreadsReady;
|
||||||
|
|
||||||
|
/// Number of work items that have not yet completed execution.
|
||||||
|
U32 mNumPendingItems;
|
||||||
|
|
||||||
/// Semaphore used to wake up threads, if necessary.
|
/// Semaphore used to wake up threads, if necessary.
|
||||||
Semaphore mSemaphore;
|
Semaphore mSemaphore;
|
||||||
|
|
||||||
|
|
@ -306,6 +319,18 @@ class ThreadPool
|
||||||
/// the queue to flush out. -1 = infinite.
|
/// the queue to flush out. -1 = infinite.
|
||||||
void flushWorkItems( S32 timeOut = -1 );
|
void flushWorkItems( S32 timeOut = -1 );
|
||||||
|
|
||||||
|
/// If you're using a non-global thread pool to parallelise some work, you
|
||||||
|
/// may want to block until all the parallel work is complete. As with
|
||||||
|
/// flushWorkItems, this method may block indefinitely if new items keep
|
||||||
|
/// getting added to the pool before old ones finish.
|
||||||
|
///
|
||||||
|
/// <em>This method will not wait for items queued on the main thread using
|
||||||
|
/// queueWorkItemOnMainThread!</em>
|
||||||
|
///
|
||||||
|
/// @param timeOut Soft limit on the number of milliseconds to wait for
|
||||||
|
/// all items to complete. -1 = infinite.
|
||||||
|
void waitForAllItems( S32 timeOut = -1 );
|
||||||
|
|
||||||
/// Add a work item to the main thread's work queue.
|
/// Add a work item to the main thread's work queue.
|
||||||
///
|
///
|
||||||
/// The main thread's work queue will be processed each frame using
|
/// The main thread's work queue will be processed each frame using
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,7 @@ if( NOT TORQUE_DEDICATED )
|
||||||
endif()
|
endif()
|
||||||
addPath("${srcDir}/platform/test")
|
addPath("${srcDir}/platform/test")
|
||||||
addPath("${srcDir}/platform/threads")
|
addPath("${srcDir}/platform/threads")
|
||||||
|
addPath("${srcDir}/platform/threads/test")
|
||||||
addPath("${srcDir}/platform/async")
|
addPath("${srcDir}/platform/async")
|
||||||
addPath("${srcDir}/platform/async/test")
|
addPath("${srcDir}/platform/async/test")
|
||||||
addPath("${srcDir}/platform/input")
|
addPath("${srcDir}/platform/input")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue