Index of Threading


1. Introduction

Zeus-Framework provides a wide spectrum of threading classes usable for

  • Multithreading
  • Synchronization of threads
  • Communicating
  • Critical sections
  • Atomic data types

The classes are implemented to be usable for Win32 and Linux platforms in the same way. To do so all kernel objects are wrapped by classes providing an unique interface for programmers.


2. Multithreading

The main class used for multithreading is TThread. It provides a nice interface for thread handing and control.

2.1 Creating threads

There are two ways to implement code for threads. The first implementation is simply extend the TThread class and overwriting the execute() method.


#include <zeusbase/System/Thread.h>

class TPhilosopherThread : public TThread
{
  public:
    ///Some constructor here
    TPhilosopherThread() : TThread()
    {
      ...
    }

  protected:
    ///Overwritten method 
    virtual void execute()
    {
      while(!isInterrupted())
      {
        ...
      }
    }

    ///callback if terminated
    virtual void onTerminated() 
    {
      printf("Philosopher [%d] killed\n", m_uiID);
    }

    ///callback if started
    virtual void onStarted() 
    {
      printf("Philosopher [%d] started\n", m_uiID);
    }
};

The second method is passing a runnable object to a TThread object. The runnable object must implement the IRunnable interface.

2.2 Controlling threads

The TThread class provides several methods to control the thread:

  • Starting and stopping threads
  • Suspending and resuming
  • Priority handling
  • Checking thread state

2.2.1 Start a thread

Starting threads is simple.


TAutoPtr<TMyThread>> ptrThread = new TMyThread();

ptrThread->start(); //starts the thread

The thread priority is inherited from the main thread. It can be set before or after starting using the


//setting priority at creating
TAutoPtr<TMyThread>> ptrThread = new TMyThread(TThread::etHigher); 
ptrThread->start(); //starts the thread

//sets a new priority while running
ptrThread->setPriority(TThread::etLow);

2.2.2 Stopping a thread

To stop threads there is more involved. The first problem occurs is the thread is blocked e.g. waiting for some resources, events or mutexes.

The first method to call for stopping a thread is signalizeStop(timeout). This is a nice way to stop the thread. It sets the interrupted flag.

On Windows platforms QueueUserAPC() is used. This asynchron procedure call will wake up the waiting thread from functions like SleepEx, WaitForMultibleObjectEx, etc.

On Linux a signal is sent to the thread. A blocking API function should be terminated with an error EINTR.

Releasing a thread object will call kill(). This will kill the thread without waiting for its termination. Be careful using it, since resources allocated won't be freed and resource leaks might occur.


TAutoPtr<TMyThread>> ptrThread = new TMyThread();
ptrThread->start();

...

ptrThread-<signalizeStop(5);
ptrThread-<kill();

2.2.3 Callbacks

The thread class provides two callback methods, witch can be overwritten by the inherited sub classes. The first callback is onStarted() and it's called before the execute method.

The second callback is onTerminated() and it is called after the execute method is exited.

These two methods can be used for instance to wait for an thread start or a thread ending.


#include <zeusbase/System/Thread.h>
#include <zeusbase/System/Event.h>

class TThreadTestObject : public TThread
{
  public:
    ...

    bool waitForExecution(Float64 dTimeOut) 
    {
      return m_rExecuted.wait(dTimeOut);
    }

  protected:
    virtual void execute() { ... }
    virtual void onStarted()
    {
      m_rExecuted.set();
    }

  private:
    ///Execution event
    TEvent& m_rExecuted;
};

Note that the callback methods are called in the context of the thread itself. Any unprotected member variable must be protected here with a mutex.

2.3 Communication

2.3.1 Thread synchronization

Since the threading is the base implementation of any cell computing application, there are some special features added to regular threads. One of this features is the implementation of a message queue for thread synchronization. For synchronization the queues must be found by other threads. The singleton ThreadManager is used for queue and thread registration.

Synchronization is called a process where as a Thread A waits until a Thread B finished with a given job.

  • Thread A has data to be processed by Thread B
  • Thread B gets notified. Thread A waits now until Thread B finishes the job.
  • Thread B receives the synchronization message and calls process() method. This method call does the job work using data provided by Thread A
  • Thread B checks the return values and wakes up thread A afterwards.
  • Thread A wakes up and continues its work.

Thread A starts the synchronization using following macros.


//context of thread A
{
  SYNCHRONIZE_METHOD(m_iThreadID_B, TTest, processWorkForA);
...
}

//This method will be called by thread B
void TTest::processWorkForA()
{
  //processes data in the context of Thread B
  ...
}

Instead of the generous macro SYNCHRONIZE_METHOD other macros can be used:

  • SYNCHRONIZE_MT_METHOD(clss, mMethod) calls a method in the context of the main thread

Thread synchronization is done over message sending. To enable the synchronization functionality each thread must dispatch messages inside the thread loop.


    virtual void execute()
    {
      while(!isInterrupted())
      {
        //The queue is a member of TThread
        m_rQueue.processObject();
        ...
      }

To enable main thread synchronization use the classes prepared for main threads (see Main Threads).

2.3.2 Asynchronous communication

There are different possibilities for this kind of communication.

The first is quite similar to the synchronization of the threads (see above). But instead of waiting for the synchronized Thread B we just continue the work at Thread A. Instead of using SYNCHRONIZE_METHOD the macro POST_METHOD can be used (ditto for main thread).

The second possibility is to use a communication queue for posting user defined objects. Thread A and Thread B must have a reference to the queue. One thread is posting to the queue where as the other thread takes messages from the queue. Bidirectional communication can be implemented using two queues.

Zeus-Framework provides different queues for thread communication:

Class Description Protected by mutex
TQueue<T> Simple queue template data type. Supports dynamic size. No
TQueueCB<T> Simple queue template data type. Uses static size (Buffer queue) No
TPriorityQueue<Key, T> Priority queue. Inserts the objects depending on the key priority. No
TAtomicQueue<T> Simple queue template data type. Supports dynamic size. Yes
TThreadMessageQueue Queue for synchronized object posting. Yes
TCommPipe Queue for interprocess and remote messaging. It takes IMessage-Objects. Yes
TMappedCommPipe Priority queue for interprocess and remote messaging. It takes IMessage-Objects. Yes

 

2.4 Main Threads

The main thread also called process thread is created if the application is started. Since this thread is attached directly to the process it must be handled specially. For instance starting is not needed. Stopping the thread will actually stop the process.

Zeus-Framework implements different main thread classes, witch all inherit from the class TAbstractMainThread:

  • TConsoleMainThread: A simple platform independent console main thread
  • TBorlandMainThread: Main thread for Win32 application using Borland Visual Component Library (VCL)

2.4.1 Console application

The console main thread can be used to create console applications or service running in the background. This class should for Win32 Application using graphical components because MS Windows uses a message queue to dispatch windows messages.

The usage of the TConsoleMainThread is fairly simple. There exists a singleton object ConsoleMainThread witch can be used:


#include <zeusbase/System/ConsoleMainThread.h>
#include <zeusbase/System/ThreadManager.h>

/*********************************************/
/*! Main function of our program
*/
/*********************************************/
int main(int /*argc*/, char* argv[])
{
  printf("Press CTRL+C to terminate.\n\n");

  //Initializes the control handler to abort using CTRL+C
  ConsoleMainThread.initControlHandler();

  //Initializes the multithreading synchronisation
  ConsoleMainThread.registerThread(ThreadManager, true);

  //Puts the main thread in and endless loop waiting for CTRL+C
  ConsoleMainThread.start();

  ConsoleMainThread.unregisterThread(ThreadManager);
  return 0;
}

2.4.1 Borland application

The class TBorlandMainThread has been developed for Borland and CodeGear applications. Borland creates a global object called Application. Calling Application->run() the GUI application is started. All windows messages are processed by internal queues of the Win32 platform.

We use those existing messaging dispatching for our thread synchronisation. First we have to initialize the singleton object. When registering the BorlandMainThread Singleton we can set the UseInternalDispatcher-Flag to true, if we want to use the internal message dispatcher of the BorlandMainThread. If not we have to call the method performAppMessage() inside our own dispatcher.


#include <zeusbase/System/ThreadManager.h>
#include <zeusbase/System/Platforms/BorlandMainThread.hpp>
...
USING_NAMESPACE_Zeus

//Initialisation of the instance
TBorlandMainThread TBorlandMainThread::m_Instance;

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
  //Initialises the multithreading synchronisation
  BorlandMainThread.registerThread(ThreadManager, true);

  //Do initialisation here
  ...

  //Starts the application loop
  Application->Run();

  BorlandMainThread.unregisterThread(ThreadManager);
  //Do cleaning here
  ...
}

2.5 Critical sections

On multithreading applications shared data members must be protected somehow. If you don't protect them seriously some runtime error might occur and they are really hard to detect. A possibility is to share no data at all, but then who can we communicate with the thread. Of course we could use the protected queues of Zeus-Frameworks only (see chapter Asynchronous communication.

Sometimes you want to do more than just passing messages or objects throu queues. Specially if you have hugh data objects, passing them with queues is not very perfomant (over a copy). Then you have to protect the data structure or even complete modification methods by critical sections.

The Zeus-Framework provides following classes:

Class Description
TCriticalSection Only one thread can enter a critical section.
TMutex Has the same behaviour like TCriticalSection. They can be named and used by different processes.
TSemaphore Semaphores allows n threads to enter the critical section. They can be named and used by different processes.