Contents


DISCLAIMER

This document describes the terms for distributing the source code of and any derived work based on the PDMLWP multitasking library. Such source code is marked with the copyright notice:
"Copyright (C) 1997 Paolo De Marino"

All the files marked with the above copyright fall either under LGPL (Library General Public License) or under GPL (General Public License) as stated at the beginning of each file, with the following exception, that ALL the people in the THANKS file must receive credit. The example*.* files are FREEWARE. You can do whatever you want with them.

All these files are distributed in the hope that they will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License and of the GNU Library General Public License along with this program; see the files COPYING and COPYING.LIB. If not, write to the Free Software Foundation, Inc., 675 Mass Ave., Cambridge, MA 02139, USA.



A couple of months ago, I found on the Net the excellent LWP package written by Josh Turpen and Sengan Short. It provided basic services such as starting a new thread, killing a thread, regulating the time slice, and so on. But soon I discovered that it didn't provide any synchronization facility between the MANY threads it could handle. Thus I decided to add a set of BASIC synchronization capabilities to it. Anyhow, as I needed a C++ interface for a C++ program I was (am!) going to write, I decided to add a complete set of objects to handle not only synchronization, but also message posting, readers/writers locks, gates, Thread objects to the thing.

So came out this package. It is basically divided in three sections:

lwpasm.S (Capital "S"!)
The assembler stuff
lwp.c
The "C" part of the business
threads.cc
The C++ interface.

C INTERFACE

This sections describes the APIs used by "C" programs. They are:
 
lwp_init Initializes the package, hooking everything
lwp_spawn Spawns a child process
lwp_kill Kills a process (can be used to suicide too)
lwp_getpid Returns the PID of the current process
lwp_thread_disable Temporarily disables the multithreading engine
lwp_thread_enable Re-enables multithreading after a call to lwp_thread_disable
lwp_yield Used to force a task switch to occur
lwp_atkill Used to set an atexit-like per-thread exit function.
lwp_sleep Puts the thread to bed for a given time
lwp_setuserptr 
lwp_getuserptr
Set and retrieve a per-thread user pointer
lwp_wait_true 
lwp_wait_false
Wait for a value to become true/false
lwp_wait_semaphore 
lwp_init_semaphore 
lwp_release_semaphore
Initialize, wait for and release a Mutex semaphore.
lwp_set_priority 
lwp_get_priority
Set and retrieve the priority of the current process.
Contents            C++ Index


int lwp_init(int irq,int speed)

This function hooks all the stuff that needs to be hooked. After you call this function, main() is operating as a task, but since it is the only task you won't notice. Both timer IRQs are supported (0 and 8). IRQ8 only supports certain speeds (2hz, 4hz, 8hz, 16hz...8192hz, see lwp.h for more). IRQ0 is programmable, and any speed can be implemented. 10hz < speed < 1024hz is recommended. This function automagically cleans up after itself when your program exits.
Returns 0 if something went wrong, != 0 if any problem was found.

NOTE: IRQ0 doesn't work under Win 95. I haven't tested it under OS/2 or Win 3.1. I think Windows hooks it.

Example:

if(lwp_init(8,RTC128))
{
    ... do your stuff ...
} else printf("Error!\n");

int lwp_spawn(void (*proc)(void *), void * arg, int stack_size,int priority)

This function spawns off other tasks. Proc is the name of a function of type void proc(void *). Stack size is used to create a stack that is used by your proc. It won't spawn if stack_size < 255. I'd suggest at least 4k, just to be safe. Your proc will lwp_kill itself when it returns, so there is no need to worry about threads joining.
Priority specifies the number of consecutive time slices the thread will have. Any number >0 will fit.
Returns the PID of the new process.

Example:

void proc(void *arg)
{
   printf("My proc is %s!\n", (char *) arg );
}
main()
{
   lwp_init(0,100);
   lwp_spawn(proc, "kewl", 4096,1);
   /* do some stuff...*/

}

int lwp_kill(int lwpid)

This function kills a process. Simple. Pass it the pid of the process and blahmo, it's dead.
A thread can safely commit suicide by calling lwp_kill(lwp_getpid())!

NOTE: main() can't be killed!
NOTE: Processes that return automagically kill themselves.

Returns 0 if couldn't kill thread, 1 if thread was correctly killed

int lwp_getpid(void)

This returns the current process's pid, useful in killing the current process.

Example:

void proc1()
{
    ...do stuff...
    lwp_kill(lwp_getpid());
    /* never returns */
}

void lwp_thread_disable(void)

This function disables task switching. It's mostly used to fix non re-entrant functions like printf, malloc, etc.

void lwp_thread_enable(void)

This function re-enables task switching after a previous lwp_thread_disable().

Example:

  lwp_thread_disable();
  printf("foo = %d\n", foo);
  lwp_thread_enable();
Take a look at the lwpstdio.h, lwpconio.h, and lwppc.h that are included with this distribution. It takes care of SOME of libc's non-reentrant functions. You don't need to wrap functions like printf with thread_disable and thread enable, because it's automagically done in the included header files. If there is a function you are using that isn't in one of the above header files and you aren't sure that it is re-entrant, wrap it with lwp_thread_disable and lwp_thread_enable, just to be safe.

void lwp_yield(void)

This function causes a task switch to happen. the main purpose of this function is to help task switches happen in tight loops with a lot of lwp_disable/enable going on, where the time window for PDMLWP to switch the threads might be VERY small. Due to suble aspects of DPMI interrupt handing, a program cannot be interrupted when it doesn't touch any of its data. So a very tight loop - or one with a lot of system calls (which disable PDMLWP) - might effectively stop multithreading. In the following example, the time window is reduced to virtually a few assembler instructions!
 
while(!kbhit())
{
    wait++;        /* Waste time...*/
}
To avoid problems, this should become:
while(!kbhit()) /* Locks the processor! */
{
    wait++;     /* Waste time...*/
   lwp_yield(); /* Give the other threads a chance too... */
}
 

void lwp_atkill(void (*proc)(void))

Sets up per-thread exit functions, in a LIFO stack. This function will be called by lwp_tcp when your thread is killed, it is either when it returns or when it is lwp_kill'ed.
NOTE: The exit function is called in an essentially non-reentrant way! You can't use any PDMLWP function from within the exit functions.DOING SO WOULD HANG THE SYSTEM! But you can call system routines, as multithreading is disabled.

Example:

void exitproca(void)

{

    printf("Finished 2\n");

}

void exitprocb(void)

{

    printf("Finished 1\n");

}

void thread(void *unused)

{

    ...

    lwp_atkill(exitproca);

    lwp_atkill(exitprocb);
    ...
    return ;
}

Output:

Finished 1

Finished 2

void lwp_sleep(unsigned int secs,unsigned short msecs)

Puts to bed your task for a given time, given at the precision of 1 thousandth of a second (the real resolution is about 50ms, because of inherent limitations of libc's ftime).
IMPORTANT: While the thread "sleeps", it doesn't eat up cycles. It simply won't receive time slices.

Example:

void periodic_check(void)
{
   while(!finished)
   {
      lwp_sleep(60*15,0);    /* Wait 15 minutes */
      autosave();
   }
}
This task wakes up every 15 minutes, and auto-saves the situation.

void lwp_setuserptr(void *usrdata)

void *lwp_getuserptr(void)

These two member functions allow the user to set a per-thread user pointer, that can be read/written by every single thread. This lets you write code accessing different data depending on the thread it executes under, without having to check the PIDs to find what to operate on. This feature may be used for instance to write down single code/multiple data threads.

void lwp_wait_true(volatile int *what)

void lwp_wait_false(volatile int *what)

These two functions play a very important role in multitasking: they allow a thread to wait for an integer to become true or false, without consuming time slices. Their most immediate use is in the implementation of a gate, i.e. of a "door" that can be opened or closed by another process.

Example:

volatile int trigger = 0;

void triggered_task(void)
{
    lwp_wait_true(&trigger);
    ... Does what it has to do...
}

void main()
{
   lwp_spawn(triggered_task,8192);

   ...Waits for some event...

   trigger = 1;    // Here it is. From this moment, the triggered_task
                   // can proceed!
   ... Does the rest of its stuff...
}

void lwp_pulse_true(volatile int *what)

void lwp_pulse_false(volatile int *what)

These two functions are useful to implement event raisers: they let any thread waiting for "*what" to become true/false pass, without actually changing the value of *what! It is, they'll set all the threads that are waiting free, but they'll stop any other thread calling lwp_wait_true/false afterwards.

void lwp_wait_semaphore(lwp_semaphore *sema)

void lwp_init_semaphore(lwp_semaphore *sema)

int lwp_release_semaphore(lwp_semaphore *sema)

These are perhaps the most important functions in the package. They allow a process to wait for a Mutex semaphore, i.e. for a semaphore that can be owned by just one thread at a time. Examples of such situations are resources such as the keyboard, the screen: only one process at a time may own one of these resources. Another kind of "resource" that needs to be locked is data: it is useful to implement locks on particular memory areas that cannot be safely accessed by two threads at the same time.

Example:

lwp_semaphore sema;

int count = 0;
void thread(void)
{
    ... Does some stuff ...
    lwp_wait_semaphore(&sema);    //Wait until semaphore is free
    count++;                      // Do something...
    lwp_release_semaphore(&sema); // Make the semaphore free again.

    ... Does some other stuff ...
}

void lwp_set_priority(unsigned priority)

Sets the priority at the value you specify for it. Don't rely on this function to protect the non-reentrant parts of your program: the priority counter is set only at next task switch.

unsigned lwp_get_priority(void)

Returns the priority count, i.e. the number of consecutive time slices the thread will receive.


C++ INTERFACE

Here we are! The nicest part of the package: the C++ interface. The package implements the following classes, which we will discuss. Contents    C Index

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().


WARNINGS:
This package is pretty stable: it has never crashed in its current version, which I tested on my 486DX2/50. It doesn't eat up much memory, neither does it eat too many cycles. BUT: there are lots of non-reentrant functions in libc and in the C++ libraries, beginning with raise()/signal(), which means that you cannot use exceptions, for they could be processed by ANY thread. Another example of non-reentrant procs are all the I/O routines, malloc,free,realloc (new and delete are non-reentrant too!). The true solution to this problem would be rewriting all the libraries, but I don't have enough time for such an enormous job. The only, obviously imperfect solution is creating small patch header files, i.e. lwpconio.h, lwppc.h, lwpstdio.h, lwpstdlib.h.

Mainly, PAY ATTENTION TO WHAT YOU DO! I have tried to shield the thing up as much as I could, isolating operator new, operator delete, malloc, realloc, free, calloc, cfree, but anyhow PAY ATTENTION to non-reentrant routines, and remember THERE ARE MANY!.

Anyhow, if you have suggestions, ideas, comments, or just want to say hello, don't hesitate to mail me.

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 ([email protected])
Via Donizetti 1/E
80127 Naples
Italy