Crystal Space 2.0.0: 6.2 Threads in Crystal Space

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## 6.2 Threads in Crystal Space

Written by Jorrit Tyberghein and Michael Gist.

The Crystal Space project as such is not thread-safe. So when using threads in combination with Crystal Space you have to be careful. However, several parts of Crystal Space are thread-safe to some degree. This is documented in this section. However, by default you must assume that some Crystal Space plugin or module is not thread-safe unless it is mentioned here.

### Threadsafe parts of Crystal Space

- SCF (see section Shared Class Facility (SCF)) class loading/unloading is safe. This means that `SCF_CREATE_INSTANCE()` and all the routines in `‘iSCF’` that have to do with loaded classes are safe.
- The plugin manager is safe, thus functions such as `csLoadPlugin`, `csLoadPluginAlways`, and `csQueryPluginClass` are safe.
- The object registry is safe, thus macros like `csQueryRegistry` are safe. The `iObjectRegistryIterator` takes a copy of the data over which it iterates so it will not even lock.
- VFS (see section Virtual File System (VFS)) is thread-safe for most operations. But do not work on the same `‘iFile’` from different threads! That is not thread-safe. Also the concept of the current directory is not really valid with threads.
- The TinyXML, XMLRead, and BinaryXML parsers are thread safe in the sense that it is okay to create and work on separate documents in separate threads. However it is not okay to work on the same document in different threads.
- The syntax services plugin is safe for the basic parsing tools (vectors, matrices, reporting errors, etc.).
- The `‘simpcon’` and `‘csconout’` output consoles are thread-safe provided auto-updating is disabled which you can set like this:

      csRef<iConsoleOutput> con = ...;
      con->AutoUpdate(false);

  Note that by default auto-updating is enabled!

- The reporter plugin is fully thread-safe.
- The standard reporter listener plugin is thread-safe for basic operations but not for initialization. That should be done from the main thread. Also note that thread-safety of the standard reporter listener depends on thread-safety of the output console that is being used.
- The following mesh object and factory loaders are thread-safe provided that the implementation of `‘iLoaderContext’` is thread-safe. Please note that the default `‘iLoaderContext’` is not thread-safe which makes all loader plugins unsafe by default.
  - Genmesh loader: Aside from a small design mistake with streams in case the new renderer is used, the genmesh loader plugin is perfectly thread-safe.

### Thread Manager

Crystal Space provides a thread manager and macros to make multi-threading as easy as possible for the user. The thread manager is a powerful and generic system which supports safe scaling of threads and importantly allows non-main threads to safely dispatch method calls to be executed by other non-main threads. In essence, if you write a plugin which correctly uses this system it will increase in parallelism as time goes by (cpus increasingly being released with larger numbers of cores) without you having to change anything to compensate (up to some point which depends on your plugin).

#### Interfaces

To declare that you want a method in your interface to be run in a thread other than the main thread, you need to make use of the `‘THREADED_INTERFACE’` macro. This is rather straightforward as shown;

    #include "iutil/threadmanager.h"

    struct iThreadTest : public virtual iBase
    {
      THREADED_INTERFACE(Test1);
      THREADED_INTERFACE1(Test2, bool b);
      THREADED_INTERFACE2(Test3, int i, float f);
    }

This will add extra information to your method, such as a special return type.

#### Class and Method declaration

Inside your class, to declare a method as being threaded you need to do two things:

- Inherit from the class `‘ThreadedCallable<>’`. This adds some extra required functionality to your class and forces you to implement some needed methods. Currently, you are only required to implement `‘GetObjectRegistry()’`.
- Make use of the `‘THREADED_CALLABLE_DECL’` macro. This is explained below.

The `‘THREADED_CALLABLE_DECL’` macro is what kicks everything off. It creates extra functions to dispatch the method call off to the thread manager, and provides a declaration to your function. It takes multiple parameters:

- The first parameter is the name of the class your method is in.
- The second parameter is the name of your method.
- The third parameter is the class of the return object, this will be explained in more detail below.
- All of the next parameters except the last two are the type followed by the name of the variables that will be passed.
- The penultimate parameter is a boolean value which determines if the method call is dispatched to the main thread(false) or a non-main thread(true).
- The final parameter is a boolean value which determines whether or not the method will return immediately (false), or if it will wait until the call is finished(true).

Be very careful when dispatching to the main thread and waiting on it to finish. E.g. If you're holding a lock which the main thread is currently waiting on, you will cause a deadlock by calling such a method.

    #include "csutil/threadmanager.h"

    class csThreadTest :
      public ThreadedCallable<csThreadTest>,
      public scfImplementation1<csThreadTest, iThreadTest>
    {
    public:
      csThreadTest(iObjectRegistry* objReg);

      iObjectRegistry* GetObjectRegistry() const
      { return objReg; }

      THREADED_CALLABLE_DECL(
        csThreadTest, Test1, csThreadReturn, true, false)
      THREADED_CALLABLE_DECL1(
        csThreadTest, Test2, csThreadReturn, bool, b, true, false)
      THREADED_CALLABLE_DECL2(
        csThreadTest, Test3, csThreadReturn, int, in, float, flo,
        true, false)

    private:
      iObjectRegistry* objReg;
    }

#### Method definition

Now that you have your class set up and methods declared, you need to define them. You do this using the `‘THREADED_CALLABLE_IMPL’` macro. The first parameter is the class name. The second is the method name and the rest are your variables as normal.

The `‘return’` here is actually a provided object, as you will read more about below. This is called `‘ret’`.

    THREADED_CALLABLE_IMPL(csThreadTest, Test1)
    {
    }

    THREADED_CALLABLE_IMPL1(csThreadTest, Test2, bool b)
    {
    }

    THREADED_CALLABLE_IMPL2(csThreadTest, Test3, int i, float f)
    {
    }

#### Return types

As mentioned above, you specify the class of the return object in the method declaration. There a restriction here; The object must implement the `‘iThreadReturn’` interface. This is already implemented in `‘csutil/threadmanager.h’` (`‘csThreadReturn’`), or you may implement this yourself.

This return object basically tells/gives you three things:

- Whether or not the method call has finished yet.
- Whether or not the method call succeeded.
- The return data from that method call.

You set the return data yourself using the provided `‘ret’` `‘iThreadReturn’` object interface in your method definition. The return data is in the form of a `csRef<iBase>` or a `‘void*’`. It is up to you which you choose to use. To indicate whether or not your function has succeeded, return a boolean value (true for success). Your return object will automatically be updated when your method has finished executing to mark that this is the case.

An example of this:

    THREADED_CALLABLE_IMPL(csClassName, MethodName2)
    {
      csRef<iInterface> obj = csPtr<iInterface>(new csImpl())
      ret->SetData(csRef<iBase>(obj));
      return true;
    }

    csClassName::MethodName()
    {
      csRef<iThreadReturn> ret = MethodName2();
      ret->Wait();
      if(ret->WasSuccessful())
      {
        csRef<iInterface> obj;
        ret->GetData(obj);
      }
    }

#### Waiting

You may have noticed something new in the example above:

    ret->Wait();

The majority of the time you will want your method call to return immediately so you can continue onwards. At some point you might want to check and wait for your method to finish before continuing. The thread manager provides a simple way to do this. Call `Wait()`, on your `‘iThreadReturn’` and it will send a message to the thread manager and then wait until your method has finished executing, at which point it will return.

### Threaded Loading

One important threaded section of Crystal Space is the threaded loader. In order to ensure that the threaded loader works correctly you must make sure that you use thread-safe versions of the following modules in Crystal Space:

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

This document was generated using texi2html 1.76.