Multithreading Utilities
Availability LightWave® 6.0
Component Layout, Modeler
Header lwmtutil.h
Global Call
LWMTUtilFuncs *mtutil;
mtutil = global( LWMTUTILFUNCS_GLOBAL, GFUSE_TRANSIENT );
The global function returns a pointer to an LWMTUtilFuncs.
typedef struct st_LWMTUtilFuncs {
LWMTUtilID (*create)(void);
void (*destroy)(LWMTUtilID mtid);
int (*lock)(LWMTUtilID mtid, int mutexID);
int (*unlock)(LWMTUtilID mtid, int mutexID);
LWMTGroupID (*groupCreate)(int count);
void (*groupDestroy)(LWMTGroupID mtgrpid);
int (*groupLockMutex)(LWMTGroupID mtgrpid,int mutexID);
int (*groupUnlockMutex)(LWMTGroupID mtgrpid,int mutexID);
LWMTThreadID (*groupAddThread)(LWMTGroupID mtgrpid, LWMTThreadFunc func,int size,void *arg);
LWMTThreadID (*groupGetThreadID)(LWMTGroupID mtgrpid, int index);
int (*groupGetThreadCount)(LWMTGroupID mtgrpid);
int (*groupRun)(LWMTGroupID mtgrpid);
int (*groupBegin)(LWMTGroupID mtgrpid);
void (*groupSync)(LWMTGroupID mtgrpid);
void (*groupAbort)(LWMTGroupID mtgrpid);
void (*groupKill)(LWMTGroupID mtgrpid);
int (*groupIsDone)(LWMTGroupID mtgrpid);
int (*groupIsAborted)(LWMTGroupID mtgrpid);
int (*groupThreadResult)(LWMTGroupID mtgrpid,int index);
int (*threadCheckAbort)();
int (*threadCheckAbort)(LWMTThreadID thrdid);
int (*threadCheckRunning)(LWMTThreadID thrdid);
LWMTThreadID (*threadGetID)(void);
void* (*threadGetArg)(void);
void* (*threadGetArgByID)(LWMTThreadID thrdid);
int (*threadGetIndex)(void);
int (*threadGetIndexByID)(LWMTThreadID thrdid);
int (*threadGetThreadCount)(LWMTThreadID thrdid);
LWMTGroupID (*threadGetGroupID)(LWMTThreadID thrdid);
void (*threadSetData)(void* ptr);
void* (*threadGetData)(void);
void (*threadSleep)(int delay);
int (*groupWait)(LWMTGroupID mtgrpid,unsigned int);
LWMTRWLockID (*rwlockCreate)(void);
void (*rwlockDestroy)(LWMTRWLockID rwlock);
void (*rwlockReadLock)(LWMTRWLockID rwlock);
int (*rwlockReadLockTimeout)(LWMTRWLockID rwlock, unsigned int spintries);
void (*rwlockReadUnlock)(LWMTRWLockID rwlock);
void (*rwlockWriteLock)(LWMTRWLockID rwlock);
void (*rwlockWriteUnlock)(LWMTRWLockID rwlock);
void (*rwlockWriteToReadLock)(LWMTRWLockID rwlock);
} LWMTUtilFuncs;
Mutex-only Functions
The multithreading global supplies a mutex (mutual exclusion) mechanism for managing
threaded execution of your plug-in. LightWave® may invoke your plug-in from multiple
threads simultaneously, which has the effect of threading your code. But when doing
certain things, for example when reading and writing global data, the threads of your code
should be executed one at a time, rather than all at once. The mutex mechanism is a way
for the threads of your code to cooperate in waiting for one another.
Think of a mutex as a dressing room, a place where a thread can have some privacy. Any
time your plug-in needs to do something synchronously (one thread at a time), you ask to
be let into the dressing room by calling lock. If another thread (another
"you") is already in that dressing room, your thread waits until the other
thread is done. Then your thread gets the dressing room, and other threads that want that
dressing room must wait for you to finish. When you're finished, you call unlock.
The LWMTUtilID returned by the create function allows you to use up to 129
separate mutexes. These are organized as one thread lock, numbered 0, and 128 generic
locks numbered 1 to 128. The distinction is purely for organizational reasons. There is
no functional difference between the locks. The number of the lock is passed as the second
argument to lock and unlock. You might think of these as 129 different
dressing rooms.
Multithreading is a complex topic. If you're unfamiliar with it, you're encouraged to
seek out a general programming text that discusses the writing of thread-safe code.
PLEASE NOTE:
|
These "Mutex-only" functions are maintained in the Multithreading Utilities Global only
for backward compatibility with existing plug-ins. They are officially deprecated, and
will be removed in some later release of LightWave®.
|
- mtid = create()
- Returns an LWMTUtilID that can be used by the lock and unlock functions. The return
value is NULL if create fails.
- destroy( mtid )
- Free resources allocated by create.
- ok = lock( mtid, index )
- Blocks until the mutex becomes available. Returns true if successful, or false if the
lock couldn't be executed for some reason. The index is an integer from 0 to 128 that
identifies which of the 129 mutexes to lock. If another thread has already called lock
for this mutex, the calling thread waits until the other thread calls unlock.
- ok = unlock( mtid, index )
- Release the mutex. If another thread has been waiting for this mutex, that thread will
execute. Returns true if successful, otherwise false.
Thread Group Functions
Cross-platform threading support is provided by the functions in the following
sections. Threads are powerful tools for the developer that allow simultaneous
paths of execution to exist within the same application. On single-core,
single-processor systems, threads typically execute synchronously. However, on
multi-processor (or multi-core) systems, threads will often execute in true
parallel, allowing the application to be more responsive than would be allowed
by a single-path execution model.
PLEASE NOTE:
|
These functions only provide the mechanism
for multi-threading. What you do within the threads you create is entirely your
concern. The responsibility lies with you, the developer, to ensure that the
functions you call from within your thread that lead to paths outside your
thread are safe to be called in that fashion (i.e., "thread-safe"). The
practice of calling LightWave® Plug-In API functions from within a thread
is not officially supported. Please be aware that such a practice
is undertaken by you at your own risk.
|
- mtgrpid = groupCreate( count )
- Threads in LightWave® are contained in, and managed by, "thread groups". Each
thread group can contain any number of threads, and must be created with the number
of threads to be managed provided in count.
- groupDestroy( mtgrpid )
- Destroys the thread group created by createGroup(). If active threads
exist in the group when it is destroyed, they will be forcibly stopped.
- result = groupLockMutex( mtgrpid, mutex )
- Attempts to gain a lock on the group's indicated mutex. Groups contain
129 mutexes, numbered from 0 to 128. The call will block until the mutex is locked.
- result = groupUnlockMutex( mtgrpid, mutex )
- Releases a lock on a group's mutex.
- thrdid = groupAddThread( mtgrpid, func, argsize, argvalue )
- Thread functions are added to the thread group using this function. The function
to be added must match the LWMTThreadFunc signature. Each thread must accept
a single void * argument, which is a pointer to the thread argument data
argvalue.
This thread argument can be specified by the argvalue parameter. If argsize
is greater than zero (i.e., it specifies the size of the data pointed to by
argvalue), then the value pointed to by argvalue will be passed to the
thread fuction by value (a copy is made). On the other hand, if argsize is
zero, then argvalue will be passed to the thread function by reference (i.e.,
the actual pointer will be provided).
As threads are added to the group, they are maintained in a suspended state. Threads
do not start until the group launches them with one of the execution functions (see
below).
- thrdid = groupGetThreadID( mtgrpid, thrdndx )
- Given a thread index (thrdndx), this function will return that thread's
identifier.
- count = groupGetThreadCount( mtgrpid )
- Returns the number of threads the specified group is managing.
- result = groupRun( mtgrpid )
- Causes the threads managed by the group to leave their suspended state, and begin
execution (simultaneously). This function blocks until all threads complete their
processing (i.e., terminate). Returns 1 upon success, and 0 if there was an error.
- result = groupBegin( mtgrpid )
- Causes the threads managed by the group to leave their suspended state, and begin
execution (simultaneously). This function does not block, returning after all threads
are started (or an error occurs). Returns 1 upon success, and 0 if there was an error.
- groupSync( mtgrpid )
- Once started by groupBegin, this function will block the calling process
until all active threads in the group complete. Calling groupBegin followed
immediately by groupSync is therefore functionally equivalent to a single
call to groupRun.
- result = groupWait( mtgrpid, delay ) (9.5+)
- Like groupSync() but with a timeout in milliseconds. Returns 1 if all
active threads in the group have completed, otherwise returns 0.
- groupAbort( mtgrpid )
- This function signals to all active threads in the group that it is time to stop.
This is a cooperative function -- the threads must periodically check for the abort
condition, and then terminate.
- groupKill( mtgrpid )
- This function summarily terminates all active threads in the group. Threads are
not afforded the opportunity for graceful termination.
- result = groupIsDone( mtgrpid )
- This query function tests to see if all the threads in the group have terminated.
- result = groupIsAborted( mtgrpid )
- This query function tests to see if the threads in the group were aborted for
some reason.
- result = groupThreadResult( mtgrpid, thrdndx )
- The result (i.e, the return code) of an individual thread can be queried using
this function.
Thread Functions
- abort = threadCheckAbort( )
- abort = threadCheckAbort( thrdid )
- This function allows the current or identified thread to check for an abort
signal from the main group. If this function returns a true value (!0), the
thread should finish up (release resources, close files, etc.), and gracefully
terminate.
- running = threadCheckRunning( thrdid )
- This function tests the identified thread to see if it is still running.
- thrdid = threadGetID( )
- Returns the identifier of the currently running thread.
- value = threadGetArg( )
- The data provided for the current thread when it was added to the group can be
retrieved using this function.
- value = threadGetArgByID( thrdid )
- The data provided for the identified thread when it was added to the group
can be retrieved using this function.
- index = threadGetIndex( )
- Returns the group index value of the current thread.
- index = threadGetIndexByID( thrdid )
- Returns the group index value of the identified thread.
- mtgrpid = threadGetGroupID( thrdid )
- Allows the thread to retrieve the identifier for the group to which it belongs.
- threadSetData( ptr )
- Sets the private data storage for the currently running thread.
- ptr = threadGetData( )
- Returns the private data storage for the currently running thread.
- threadSleep( timeout )
- Causes the thread to "sleep" for a specified number of milliseconds. In most
cases, this causes the thread to release its timeslice, avoiding potential hammering
of the CPU(s).
Read/Write Lock Functions (9.5+)
- rwlock = rwlockCreate( )
- Creates a new read/write lock.
- rwlockDestroy( rwlock )
- Destroyes a read/write lock. The lock must not be in use by any thread.
- rwlockReadLock( rwlock )
- Obtain a read lock. The call will block if there is a write lock. A thread
can have multiple read locks on the same rwlock. Each read lock must be unlocked.
If a thread holds a write lock, it can obtain a read lock, but it will be treated
as another write lock. Note that read locks can not be upgraded to a write lock.
- rwlockReadLockTimeout( rwlock, spintries )
- Attempts to obtain a read lock, with a timeout if no read lock can be obtained.
The timeout uses a spin lock. The maximum number of attempts to get a read lock is given
by spintries. The first few attempts will be made without any waiting between
tries. Subsequent attempts may sleep for a short period, under the assumption that the
write lock is busy for a while. The number of fast tries and the delay between the slower
tries are intentionally not defined. A spintries values of UINT_MAX is
treated as an infinite number of attempts, making it exactly equivalent to
rwlockReadLock().
- rwlockReadUnlock( rwlock )
- Releases a read lock. Each read lock obtained must be released.
- rwlockWriteLock( rwlock )
- Gets a write lock. Each write lock must be unlocked. A thread can have multiple write locks
on the same rwlock. A thread must not attempt to get a write lock while holding a read lock.
The thread must release the read lock before obtaining the write lock.
- rwlockWriteUnlock( rwlock )
- Releases a write lock. Each write lock obtained must be released, if not downgraded to
a read lock.
- rwlockWriteToReadLock( rwlock )
- Downgrades a write lock to a read lock. The thread will keep a lock throughout the conversion.
This allows a thread to safely switch from writing to reading without another thread
intervening with a new write lock. Note that once converted to a read lock, it can not be
upgraded to a write lock.
History
LightWave® 9.0 introduced the Thread Group and Thread
support functions.
LightWave® 9.5 added read/write locks.
Mutex-only Example (legacy)
This code fragment outlines the sequence of steps you'd take to use a mutex.
#include <lwmtutil.h>
LWMTUtilFuncs *mtutil;
LWMTUtilID mtid;
mtutil = global( LWMTUTILFUNCS_GLOBAL, GFUSE_TRANSIENT );
if ( !mtutil )
...global not available, do this some other way...
/* create the mutex */
mtid = mtutil->create();
...
/* enclose critical code (code that must run synchronously) in
matching lock()/unlock() calls */
if ( mtutil->lock( mtid, 0 )) {
...do something that can't be threaded...
mtutil->unlock( mtid, 0 );
}
...
/* free the mutex when you no longer need it */
if ( mtid ) mtutil->destroy( mtid );
Mutex-only Example
This code fragment illustrates the use of Thread Group functions for
performing mutex-only activities.
#include <lwmtutil.h>
LWMTUtilFuncs *mtutil;
LWMTGroupID mtgrpid;
mtutil = global( LWMTUTILFUNCS_GLOBAL, GFUSE_TRANSIENT );
if ( !mtutil )
...global not available, do this some other way...
// create the thread group to be used just for its mutex array
mtgrpid = mtutil->groupCreate(1);
...
// enclose critical code (code that must run synchronously) in
// matching lock()/unlock() calls
if ( mtutil->groupLockMutex( mtgrpid, 0 )) {
...do something that can't be threaded...
mtutil->groupUnlockMutex( mtgrpid, 0 );
}
...
// free the thread group when you no longer need it
if ( mtgrpid ) mtutil->groupDestroy( mtgrpid );
Threading Example
The following code fragment shows an example of using threads.
#include <lwmtutil.h>
...
LWMTUtilFuncs *mtutil;
...
int my_thread_func(void *arg)
{
if(!mtutil)
return -1;
// the thread was added to the group with a NULL
// argument value, so 'arg' will be NULL. we ignore
// it.
while(!mtutil->threadCheckAbort())
{
...process thread function...
// be a well-behaved thread
mtFuncs->threadSleep(100);
}
return 0;
}
...
mtutil = global( LWMTUTILFUNCS_GLOBAL, GFUSE_TRANSIENT );
if ( !mtutil )
...global not available, do this some other way...
// create the thread group, add a thread, and launch it
LWMTGroupID mtgrpid;
mtgrpid = mtutil->groupCreate(1);
if(mtgrpid)
{
mtutil->groupAddThread(mtgrpid,my_thread_func,0,NULL);
mtutil->groupBegin(mtgrpid);
}
...
// when we're done, shut down the thread and release the group
if(mtutil && mtgrpid)
{
mtutil->groupAbort(mtgrpid);
while(!mtutil->groupIsDone(mtgrpid))
mtutil->threadSleep(100);
mtutil->groupDestroy(mtgrpid);
}
...
|