C++ INTERFACE

Class CritSect

Is just a shorthand for lwp_thread_disable/enable: CritSect objects stop multithreading within their scope of existence. The most important difference is that CritSect objects are re-entrant - i.e., if you create two CritSect one within the other, control will be released at the end of the outermost block. (This is particularly useful because, as you won't have to check anything, you can't make mistakes ;)
 
int count=0;
void func(void)

{
 ... Some stuff...
 do {
     CritSect sect;
     count++;
 } while(0);
 ... More stuff ...
}

Classes MutexSemaphore / MutexSemaphoreLock

This is the simplest kind of synchronization object: it allows only one thread at a time access to a resource. It implements a recursively lockable mutex, which is its safest form.

Example

MutexSemaphore sema; // Initializes itself on its own.
void func(void)
{
 do {
   MutexSemaphoreLock lock(sema);
   ... Do your private stuff with the semaphore locked...
 } while(0);
 ... Now semaphore is automagically unlocked...
}
But, as it is recursively lockable, even the following code would have worked without hanging the machine.
MutexSemaphore sema;
void func(void)
{
 do {
   MutexSemaphoreLock lock(sema);
   do_stuff();
 } while(0);      // The semaphore is unlocked only here, as one 
                  //might expect.
}

void do_stuff(void)
{
   MutexSemaphoreLock lock(sema); // The semaphore was already locked!
   ... Whatever this function does...
}                                 // And is NOT unlocked here!

class CountSemaphore

This class allows for a different kind of semaphore: a thread may allow a definite number of processes to pass. If you were writing a game, you might want the monsters to show up at certain moments, one after the other, or perhaps two at the same time, but you surely don't want all of them to materialize simultaneously, neither do you want to wait for each monster to die before making another appear. So...
CountSemaphore monsters_sema;
void monster(void *unused)
{
   monsters_sema.Wait();  // Wait for ok from theGame()

   ...Boo! Eat the player! ...
}

main()
{

... Multithreading is initialised, all your stuff is done...

   if(level == 1 )     // Spawn 10 (sleeping) monsters.
   {
       for(int i = 0; i < 10; i++) lwp_spawn(monster,(void*)0,8192,1);
   } else              // Spawn 20 (sleeping) monsters.
   {
       for(int i = 0; i < 20; i++) lwp_spawn(monster,(void*)0,8192,1);
   };

   ... At a certain point, you call the main game loop ...

   theGame();
}

void theGame(void)
{
    ... You want to materialize a certain number of monsters ...
    if(level == 1)
    {
       monsters_sema.Clear();  // Let one process pass on...
    } else
    {
       monsters_sema.Clear(2); // Wake up two processes!
    };
}

Class SerializedQueue<T>

This class is used by the Thread class to send/receive messages in a serialized (FIFO) way, but can also be used as an example of how semaphores can be used to serialize access to objects. The serialization of the access is obtained with two semaphores: a first mutex semaphore that enqueues all accesses (remember that in a queue there usually is no "peek" function, but only "push" and "pop", and thus all accesses modify it), and a Count semaphore for "poppers": when a thread wants to pop an object from the bottom of the queue, and there is no object there, it goes into a waiting state, cleared by the "pushing" of an object by another thread.
 

class Gate

Gates are checkpoints that can either allow all threads to pass through
a point, or to stop them all from proceeding.

Example: (see the file test/example6.cc too)

If in that game you wanted to introduce a "pause" key, you could write:

Gate pauseGate(1); // Initialize it to be open!

main()
{

 ... Some stuff ...
 if( pause_key_pressed() )
 {
     pauseGate.Close(); // Stop all the other game threads!

     while(!kbhit()) lwp_yield(); // Wait for another key pressure...

     pauseGate.Open(); // Re-open the gate
 }

 ... Rest of the game ...
}

void game(void)
{
   ... There is some code...
   while(!game_over)
   {
      pauseGate.Wait(); // Wait for the gate to be opened, or pass on if it
                        // is already open.
      ... Play ...
   };
}

void monster(void)
{
   ... There is some code to draw the monster ...
   while(alive)
   {
      pauseGate.Wait(); // Wait for the gate to be opened, or pass on
                        // if it is already open.
      ... Move the monster ...
   };
   ... Make the monster disappear ...
}

Class Event

Events allow to let all the threads waiting for it to pass simultaneously when the event is Raise()d.

See the file test/example9.cc for an example of using the Event class.
 

Classes ReadWriteSemaphore/ReadLock/Writelock

Often in multithreading there are resources that many threads can "read" simultaneously without problems, but that can be accessed for writing by only one thread at a time, and can't be read when they're being written to. An example of such a resource is a big structure: many can read, but only one can write. In these cases, the best thing to do is using a ReadWriteLock.

Interface to this class is very simple, as the example shows:

Example (see the file test/example5.cc)

ReadWriteSemaphore sema;

int ToBeProtected;
void thread1(void)
{
  int tmp;
  ... Some stuff ...
  do { 
     ReadLock lock(sema);
     tmp = ToBeProtected;
  } while(0);
  ... Rest of the stuff ...
}
void thread2(void)
{
  int tmp;
  ... Some stuff ...
  do { 
     WriteLock lock(sema);
     ToBeProtected++;
  } while(0);
  ... Rest of the stuff ...
}
ATTENTION: Readers/Writers lock ARE NOT FAIR LOCKS! If there are many writers, readers will probably starve, as Writers have precedence on Readers!
 

Class Thread

This is the heart of the C++ package: the Thread class allows for a simple C++ creation of independent light weight processes (whence the name PDMLWP!). The interface to the Thread class is simple: to create a new Thread you have to derive a new class from the base class:
class MyThread : public Thread
{
public:
   MyThread(unsigned int stacksize) : Thread(stacksize) { };
   virtual void prepare(void); // Starts as soon as the thread is created
   virtual void execute(void); // Starts when the thread is "start()"ed
   virtual void cleanup(void); // Starts when execute() returns
}
The three virtual methods prepare(), execute() and cleanup(), overloaded, provide the actual thread code. They default to doing nothing. The reason they are three is to make life easier to programmers. In fact, they are all called at very specific times. NOTE: the cleanup() function is NOT called when the kill() member is called, and any objects created by the thread REMAIN EXISTENT UNLESS YOU EXPLICITLY DESTROY THEM SOMEWHERE.

But Thread object don't provide only these services: their most important role is allowing threads to exchange messages. A message is simply a pointer to void, that you "post()" to another thread. The Thread objects provides two methods to do it:

void postMessage(void *) and
static void postMessage(void *,Thread& dest).

The first can be used so:

    myThread.postMessage("Hello!");
Sends the message "Hello!" to the thread myThread. In the same way, you might call
    Thread::postMessage("Hello",myThread);
These two forms are synonyms.

Receiving a message is very simple too, but one thing must be understood: you send just void*, but you receive Message objects, containing another important datum, i.e. the sender. Thus, to receive that message, and print all the relevant data, you should write:

void myThread::execute(void)
{
 do {
    Message theMessage = getMessage(); // Inside execute(), you have
                                       // access to member functions!
    if(theMessage.Contents() == 0) break;

    printf("Message is: %s from thread with pid(%i)\n",
           (char*)(theMessage.Contents()),
           theMessage.Source()->getPid());

 } while(1);
}
NOTES:
  1. The getMessage() method waits for a message to be sent, THEN returns it. Pay attention to deadlocks!
  2. Messages arrive in the order in which they were posted. I was very careful about it: they are put in a queue (obviously, in a serialized queue).
  3. In fact, you are not limited at all to sending char*! You can send ANY pointer with the postMessage()/getMessage() method.
  4. When you perform a getMessage(), the message is removed from the queue. If you want to check how many message are waiting to be processed, use the waitingMessages() method, or the noMessages() to check whether the messages queue is empty.
Just one more thing: there is a global MainThread object, named theMainThread, which is exactly what it pretends to be: the object returned when you call Thread::currentThread() from the main(). It is initialized by
   void InitLwp(speed)
which uses IRQ 8, the most programmable IRQ, to give/receive control, and destroyed by
   void DoneLwp(void)
that should be more or less the first and the last thing you do in your main().

Standard Disclaimer

There are absolutely no guarantees, expressed or implied, on anything that you find in this document. I cannot be held responsible for anything that results from the use or misuse of this document.

Paolo De Marino (paolodemarino@usa.net)
Via Donizetti 1/E
80127 Naples
Italy