Crystal Space 2.0.0: 4.6.2.2 Threading library

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

#### 4.6.2.2 Threading library

The threading library is a collection of routines and classes for creating multithreaded and thread safe code and programs.

Remember that just because you can make something multithreaded and threading safe does not mean you should do so, or doing so is a good idea and will give you a lot of benefit. Use with care, when appropriate.

All threading related classes and operations are in the `CS::Threading` namespace.

#### Atomic operations

Atomic operations are the basic and smallest operations behind threading safe code. Atomic in this context means that they are guaranteed to complete with a deterministic result in relation to other threads and even cores doing other atomic operations on the same data.

Atomic operations in Crystal Space is found in `CS::Threading::AtomicOperations` class in `‘csutil/threading/atomicops.h’`. There are a few different atomic operations, namely:

Operation | Function | Description
Set | `AtomicOperations::Set` | Atomically set the content of a pointer or 32 bit integer variable and return the value it had before setting it.
Read | `AtomicOperations::Read` | Read content of pointer or 32 bit integer variable.
Compare and Set (CAS) | `AtomicOperations::CompareAndSet` | Atomically compare current content of pointer or 32 bit integer variable and if it is equal to a given value exchange it. Returns the value of the variable before comparison. Atomic CAS operations are among the most used and most useful when it comes to building locking primitives or lock-free data structures.
Increment | `AtomicOperations::Increment` | Atomically increment 32 bit integer variable and return its value prior to incrementation.
Decrement | `AtomicOperations::Decrement` | Atomically decrement 32 bit integer variable and return its value prior to decrementation.

#### Threads

A thread is the smallest unit of execution when looking at multithreaded execution. Each thread is a "lane" of sequential execution and by having multiple threads executing at the same time you get the effect of simultaneous execution.

As this concept of simultaneous execution can be a bit confusing at first there are two important things to remember

1. The operating system have full control over how and when threads are executed. This means that you can never be certain of if two threads are executed on same or different processors or cores if you have dual processor/core system, you cannot rely on different threads executing a specific order.

   A more important result of this is that you cannot rely on or even know when one thread is interrupted to let another one execute. This means that whenever you operate on data shared between two or more threads you must use one of the syncronization primitives or atomic operations.

2. Threading is not always the answer. Wisely used in the correct cases it can provide a significant speedup and/or make the code easier, however in the wrong spots it can make the code buggy, slow and complicated. Use with care!

To create a thread you first need to implement the interface `CS::Threading::Runnable` and instance it. Then create an instance of `CS::Threading::Thread` to start a new thread executing the runnable instance.

Use the `Start()` and `Stop()` methods of the thread to control its execution and `Wait()` to wait until it finishes execution.

Here is an example of a simple thread and creation

    #include "crystalspace.h"

    using namespace CS::Threading;

    class MyRunner : public Runnable
    {
      void Run ()
      {
        int i = 0;
        while (i < 100)
        {
          // Wait for a while
          csSleep (1000);

          // Print a message. Notice that this is potentially not thread safe!
          csPrintf ("Thread printing\n");
        }
      }
    };

    int main (int argc, char* argv[])
    {
      // Instance runnable and thread
      csRef<Runnable> r; r.AttachNew (new MyRunner);
      csRef<Thread> thread; thread.AttachNew (new Thread (r));

      // Start the thread
      thread->Start ();

      // Wait for a while to let it run
      csSleep (500);

      // Wait for the thread to finish running
      thread->Wait ();
    }

As you can see in this example it is not doing anything useful while the thread is running. In a real application you would do something useful while the thread is for example waiting for IO.

#### Syncronization primitives

The threading library provides two different thread syncronization primitives. The two works differently and do not have the same purpose. Which primitive to use depends on the situation at hand.

Type | Class | Usage
Mutex | `Mutex` | Mutually exclusive access to a shared resource. N threads share a common buffer and only one thread should be allowed to modify it at a time.
Condition | `Condition` | Signal that a condition is fullfilled. One or more threads wait for a condition to be signaled by a source.

To note is that a condition always is used together with a mutex, which also should be shared between the threads.

The example below shows how a mutex and a condition can be used to distribute work units between two threads. Note, this sample works but is not fully complete. For a better implementation, look at `CS::Threading::ThreadedJobQueue`

    #include "crystalspace.h"

    using namespace CS::Threading;

    // Shared data
    csFIFO<int> queue;

    // Mutex to protect it
    Mutex queueMutex;
    Condition newItemInQueue;

    class MyRunner : Runnable
    {
      void Run ()
      {
        while (true)
        {
          // Get an item from queue
          int myData;

          {
            // Make sure we lock the queue before trying to access it
            MutexScopedLock lock (queueMutex);

            // Wait until we have an item
            while (queue.Length() == 0)
              newItemInQueue.Wait (queueMutex);

            // When we get here we know that:
            // 1. newItemInQueue have been signaled
            // 2. We have the queueMutex locked
            // 3. The queue is not empty

            // Get the data
            myData = queue.PopTop ();
          }

          // Process it
          csPrintf ("Processing data: %d\n", myData);
          csSleep (400);
        }
      }
    };

    int main (int argc, char* argv[])
    {
      // Instance runnable and thread
      csRef<Runnable> r; r.AttachNew (new MyRunner);
      csRef<Thread> thread; thread.AttachNew (new Thread (r));

      // Start the thread
      thread->Start ();

      // Give it some data
      for (int i = 0; i < 100; ++i)
      {
        // Prepare the data
        int myData = (i * 10 + 31)  % 47;

        {
          //Lock the queue and insert our data
          MutexScopedLock lock (queueMutex);
          queue.Push (myData);
        }

        // Notify one waiting threads, let them compete over who takes it
        newItemInQueue.NotifyOne ();
      }

      ...
    }

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

This document was generated using texi2html 1.76.