[ < ] [ > ]   [ << ] [ 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.