HDK
|
The easiest form of multithreading to perform inside the HDK is with UT_ThreadedAlgorithm. This class maintains a thread pool to allow for efficient data-parallel execution. It also ties into Houdini's -j option so will respect command line requests for how much threading the user wants performed.
While you can setup and invoke the UT_ThreadedAlgorithm::run method yourself, this involves a lot of boiler plate code to marshal parameters in and out of your threaded procedures. To greatly simplify the setup there are a series of THREADED_METHOD1 macros which can be used to make class members multithreaded.
Consider this class, FOO, which has a single-threaded method bar which one wants to make multithreaded.
We want to divide bar's for loop up onto separate threads.
Callers of FOO::bar() will automatically trigger a multithreaded execution of FOO::barPartial(). The UT_JobInfo class allows each instance to find out how many threads are active and which thread it is, from which it can decide on its own load balancing approach. The divideWork() method makes it easy to do a equal assignment of resources.
Callers can avoid multithreading by calling FOO::barNoThread() to invoke only a single copy of FOO::barPartial. Likewise, in this case, if myLength > 100 is false, no threading occurs. Providing a lower bound is often useful to avoid threading overhead from dominating with small work loads.
Note the placement of commas in the THREADED_METHOD2 macro. Each parameter type is followed by a space. Also note that these are marshalled by value into the different threads so large structures should be passed by reference or pointer.
Note that the return type is void. If you need to return some data you can pass in a pointer to store the result. This usually requires locking. Consider this example using FOO again, but returning the sum of the myData.
Here the UT_AutoJobInfoLock is used to create a lock that lasts for the scope of that variable. This sort of auto lock is useful as you don't have to worry about making sure it is released to avoid deadlocks. If you want to manually lock, you can use the UT_JobInfo::lock and UT_JobInfo::unlock methods. This are better than using your own UT_Lock because they becom no-ops when the UT_ThreadedAlgorithm is run in single threaded mode.
An example of THREADED_METHOD in use can be found in SIM/SIM_GasAdd.C and SIM/SIM_GasAdd.h.
Often it is useful to have static data that you want to store. Static data is usually a bad thing in multithreaded applications because different threads might clobber on your shared data. However, in practice, the static data is usually semantically independent between threads. If you don't want it shared between threads, you can turn it into thread local storage.
Different compilers have different native ways of defining thread local storage. If you are on one platform, this might be the best approach. Houdini provides its own cross platform thread local storage implementation via UT_ThreadSpecificValue.
This function will return the previous value passed to it. The default of an int in UT_ThreadSpecificValue is 0, so the first call will return 0. It is thread safe, meaning that the behavior of one thread will not affect another.
The UT_Thread class provides lower level functionality for explicitly creating threads. It is recommended to use UT_ThreadedAlgorithm instead wherever possible.
Conceptually, when a thread is spawned, two tasks are created: one in the new child thread, and another in parent thread. Then any code that runs in the two new child tasks may serialize (or lock) against each other for non-thread-safe resources. In order to do this, UT_TaskScope objects must be created in the child tasks before any further HDK code is run. This allows UT_TaskLock objects to be acquired in a deadlock-free manner. UT_Thread::startThread() will automatically do this for the child thread, but you must do this explicitly if you run any code that calls the HDK in the parent thread. UT_ThreadedAlgorithm already handles all of this internally so nothing extra needs to be done when using UT_ThreadedAlgorithm.
Here is an example of manually creating threads.