Plug-ins can utilize threaded code if certain guidelines are followed. Below are some notes that provide guidance for this issue:
MThreadPool gives access to a pool of threads to which tasks can be assigned. The number of tasks does not have to equal the number of threads, in fact for load balancing it is usually better if the number of tasks exceeds the number of threads. Maya will internally balance the work among the threads for optimal efficiency. The number of threads in the pool is equal to the number of logical processors. It is not necessary to delete the thread pool after each usage, and for performance reasons it is better not to do so, since the threads will be put to sleep when a parallel region finishes, which means they can be restarted quickly.
MThreadAsync allows the creation of one of more threads that can run for a long time. They are not drawn from the thread pool created and managed by MThreadPool, but are independent threads. These threads can be used for longer running tasks. Since they are not created from the thread pool, the number and workload of such threads should be managed carefully to avoid oversubscription issues, where the number of busy threads exceeds the hardware resources available.
MMutexLock is a locking primitive that can be used with both MThreadPool and MThreadAsync threads. It allows standard mutex locking of threads.
MSpinLock is a lock that spin-waits, so can be more efficient than a mutex lock in situations where the lock is likely to be held for a very short time. However since the lock spin waits, it is a heavy CPU consumer, and should not be used when locks are likely to be held for a long time.
The following example demonstrates how to find primes using a serial and a threaded approach. The threaded approach uses the MThreadPool class.
#include <math.h>
#include <maya/MIOStream.h>
#include <maya/MSimple.h>
#include <maya/MTimer.h>
#include <maya/MGlobal.h>
#include <maya/MThreadPool.h>
DeclareSimpleCommand( threadTestCmd, PLUGIN_COMPANY, "2011");
typedef struct _threadDataTag
{
int threadNo;
long primesFound;
long start, end;
} threadData;
typedef struct _taskDataTag
{
long start, end, totalPrimes;
} taskData;
#define NUM_TASKS 16
// No global information used in function
static bool TestForPrime(int val)
{
int limit, factor = 3;
limit = (long)(sqrtf((float)val)+0.5f);
while( (factor <= limit) && (val % factor))
factor ++;
return (factor > limit);
}
// Primes finder. This function is called from multiple threads
MThreadRetVal Primes(void *data)
{
threadData *myData = (threadData *)data;
for( int i = myData->start + myData->threadNo*2; i <= myData->end; i += 2*NUM_TASKS )
{
if( TestForPrime(i) )
myData->primesFound++;
}
return (MThreadRetVal)0;
}
// Function to create thread tasks
void DecomposePrimes(void *data, MThreadRootTask *root)
{
taskData *taskD = (taskData *)data;
threadData tdata[NUM_TASKS];
for( int i = 0; i < NUM_TASKS; ++i )
{
tdata[i].threadNo = i;
tdata[i].primesFound = 0;
tdata[i].start = taskD->start;
tdata[i].end = taskD->end;
MThreadPool::createTask(Primes, (void *)&tdata[i], root);
}
MThreadPool::executeAndJoin(root);
for( int i = 0; i < NUM_TASKS; ++i )
{
taskD->totalPrimes += tdata[i].primesFound;
}
}
// Single threaded calculation
int SerialPrimes(int start, int end)
{
int primesFound = 0;
for( int i = start; i <= end; i+=2)
{
if( TestForPrime(i) )
primesFound++;
}
return primesFound;
}
// Set up and tear down parallel tasks
int ParallelPrimes(int start, int end)
{
MStatus stat = MThreadPool::init();
if( MStatus::kSuccess != stat ) {
MString str = MString("Error creating threadpool");
MGlobal::displayError(str);
return 0;
}
taskData tdata;
tdata.totalPrimes = 0;
tdata.start = start;
tdata.end = end;
MThreadPool::newParallelRegion(DecomposePrimes, (void *)&tdata);
// pool is reference counted. Release reference to current thread instance
MThreadPool::release();
// release reference to whole pool which deletes all threads
MThreadPool::release();
return tdata.totalPrimes;
}
// MSimple command that invokes the serial and parallel thread calculations
MStatus threadTestCmd::doIt( const MArgList& args )
{
MString introStr = MString("Computation of primes using the Maya API");
MGlobal::displayInfo(introStr);
if(args.length() != 2) {
MString str = MString("Invalid number of arguments, usage: threadTestCmd 1 10000");
MGlobal::displayError(str);
return MStatus::kFailure;
}
MStatus stat;
int start = args.asInt( 0, &stat );
if ( MS::kSuccess != stat ) {
MString str = MString("Invalid argument 1, usage: threadTestCmd 1 10000");
MGlobal::displayError(str);
return MStatus::kFailure;
}
int end = args.asInt( 1, &stat );
if ( MS::kSuccess != stat ) {
MString str = MString("Invalid argument 2, usage: threadTestCmd 1 10000");
MGlobal::displayError(str);
return MStatus::kFailure;
}
// start search on an odd number
if((start % 2) == 0 ) start++;
// run single threaded
MTimer timer;
timer.beginTimer();
int serialPrimes = SerialPrimes(start, end);
timer.endTimer();
double serialTime = timer.elapsedTime();
// run multithreaded
timer.beginTimer();
int parallelPrimes = ParallelPrimes(start, end);
timer.endTimer();
double parallelTime = timer.elapsedTime();
// check for correctness
if ( serialPrimes != parallelPrimes ) {
MString str("Error: Computations inconsistent");
MGlobal::displayError(str);
return MStatus::kFailure;
}
// print results
double ratio = serialTime/parallelTime;
MString str = MString("\nElapsed time for serial computation: ") + serialTime + MString("s\n");
str += MString("Elapsed time for parallel computation: ") + parallelTime + MString("s\n");
str += MString("Speedup: ") + ratio + MString("x\n");
MGlobal::displayInfo(str);
return MStatus::kSuccess;
}