Locale Info Node Editor Func Globals Table of Contents

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);
    }
    ...