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.
So came out this package. It is basically divided in three sections:
| 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. |
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");
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...*/
}
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
Example:
void proc1()
{
...do stuff...
lwp_kill(lwp_getpid());
/* never returns */
}
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.
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... */
}
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);
...
Output:
Finished 1 Finished 2
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.
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...
}
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 ...
}
int count=0;
void func(void)
{
... Some stuff...
do {
CritSect sect;
count++;
} while(0);
... More stuff ...
}
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!
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!
};
}
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 ...
}
See the file test/example9.cc for an example of using the Event class.
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 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.
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:
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().
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.