home *** CD-ROM | disk | FTP | other *** search
- [11. Sample Program Code]
- A SAMPLE EMBEDDED DOS MULTITHREADED PROGRAM
- ════════════════════════════════════════════════════════════════════════
- Embedded DOS's kernel API is actually easy to use. Let's start with a
- simple example that does not yet have real-time elements; the "Hello
- World" of multithreaded programs. This program will load, and execute
- in the context of its own thread (its "root" thread). Then it will
- allocate two other threads that will busy themselves printing their
- own output to the screen. The output will be intermixed, because the
- threads all have the same priority and will each get a bit of CPU time
- before the scheduler selects the next thread for execution.
-
- #include <kernel.h>
- #include <system.h>
- #include <conio.h>
-
- THREAD FirstThread ()
- {
- while (TRUE) {
- putch ('1'); // print a character to the screen.
- }
- } // FirstThread
-
- THREAD SecondThread ()
- {
- while (TRUE) {
- putch ('2'); // print a character to the screen.
- }
- } // SecondThread
-
- VOID main ()
- {
- HANDLE Thread1, Thread2;
- UCHAR ch;
-
- Thread1 = AllocateThread (FirstThread); // start first thread.
- Thread2 = AllocateThread (SecondThread); // start next thread.
-
- ch = getch (); // wait for a keypress.
-
- AbortThread (Thread1); // stop first thread.
- AbortThread (Thread2); // stop second thread.
- printf ("All worker threads have been aborted.\n");
- } // main
-
- This example illustrates several of the problems that must be addressed
- when writing multithreaded programs. Let's discuss them, one by one.
-
- First, you'll notice there are three threads in this system. The first
- one executes the main function. This thread gets created automatically
- by Embedded DOS when the program is loaded. The other two threads are
- created by the first thread in the calls to the AllocateThread kernel
- function. In each case, the address of the function to be executed by
- the new thread is passed to AllocateThread by simply calling it by name.
-
- Next, after the two additional threads are created, they begin running
- in the system, independent from the root thread that executed the main
- function. These threads will continue to run in the system until they
- are aborted by the main thread with the calls to Abort Thread. Threads
- may also deallocate themselves by calling DeallocateThread.
-
- There is a problem with calling AbortThread on these threads, however.
- You'll note that each thread calls the C library function putch to write
- to STDOUT. This library function makes a DOS call, and Embedded DOS
- automatically allocates a stack for purposes of handling the call
- reentrantly. Because the main program's AbortThread call could destroy
- the thread while the internal resources remain allocated, it is possible
- that these resources will never be cleaned up.
-
- We'll solve this problem in the next example. A better approach to
- destruction of threads is slightly more complicated, but necessary.
- We will define a global variable that the main function can set to
- TRUE when it wants to signal the threads to kill themselves, at the
- next safe opportunity. Here it is, our better version of the program:
-
- #include <kernel.h>
- #include <system.h>
- #include <conio.h>
-
- static USHORT StopThreads=FALSE;
- static USHORT ThreadCount=0;
-
- THREAD FirstThread ()
- {
- ThreadCount++;
- while (!StopThreads) {
- putch ('1'); // print a character to the screen.
- }
- ThreadCount--;
- DeallocateThread (); // terminate this thread.
- } // FirstThread
-
- THREAD SecondThread ()
- {
- ThreadCount++;
- while (!StopThreads) {
- putch ('2'); // print a character to the screen.
- }
- ThreadCount--;
- DeallocateThread (); // terminate this thread.
- } // SecondThread
-
- VOID main ()
- {
- HANDLE Thread1, Thread2;
- UCHAR ch;
-
- Thread1 = AllocateThread (FirstThread); // start first thread.
- Thread2 = AllocateThread (SecondThread); // start next thread.
-
- ch = getch (); // wait for a keypress.
-
- StopThreads = TRUE; // signal threads.
-
- while (ThreadCount > 0) ; // spin waiting for threads.
-
- printf ("All worker threads have terminated themselves.\n");
- } // main
-
- In the above example, we also maintained a counter of the number of
- threads in the system. When the child threads enter the system, they
- increment the counter. They decrement the counter before they
- terminate, making it easy for the main function to know when they
- have terminated themselves.
-
- We can still improve on this, for we should realize that on
- occasion, we can't realy on the C compiler to generate code that
- is multitasking- safe, or MT-safe. Can you spot the problem?
- It is the use of the autoincrement and autodecrement operators
- on the ThreadCount variable. While setting the StopThreads
- variable is MT-safe, the increment and decrement operators are
- potential hazards in real-time code because the C compiler may
- generate multiple instructions for them that could potentially
- be interrupted by context switches. We could use features of
- Embedded DOS to temporarily suspend context switching around these
- operators, but a much more elegant solution to the problem, and one
- that shows how kernel events are used, is shown below:
-
- #include <kernel.h>
- #include <system.h>
- #include <conio.h>
-
- static USHORT StopThreads=FALSE;
- static HANDLE Event1, Event2;
-
- THREAD FirstThread ()
- {
- while (!StopThreads) {
- putch ('1'); // print a character to the screen.
- }
- SetEvent (Event1); // tell root thread we're done.
- DeallocateThread (); // terminate this thread.
- } // FirstThread
-
- THREAD SecondThread ()
- {
- while (!StopThreads) {
- putch ('2'); // print a character to the screen.
- }
- SetEvent (Event2); // tell root thread we're done.
- DeallocateThread (); // terminate this thread.
- } // SecondThread
-
- VOID main ()
- {
- HANDLE Thread1, Thread2;
- UCHAR ch;
-
- AllocateEvent (&Event1); // allocate events & clear them.
- AllocateEvent (&Event2);
-
- Thread1 = AllocateThread (FirstThread); // start first thread.
- Thread2 = AllocateThread (SecondThread); // start next thread.
-
- ch = getch (); // wait for a keypress.
-
- StopThreads = TRUE; // signal threads.
-
- WaitEvent (Event1); // wait for thread 1.
- WaitEvent (Event2); // wait for thread 2.
-
- DeallocateEvent (Event1); // release events to EDOS.
- DeallocateEvent (Event2);
-
- printf ("All worker threads have terminated themselves.\n");
- } // main
-
- We solved this problem by using MT-safe Embedded DOS kernel
- objects called events to perform the synchronization. Event
- objects have a state; they are either in the set state or in the
- cleared state. Events can be set, cleared, pulsed, or waited on
- by any number of threads. When an event is set, or momentarily
- set by calling the PulseEvent function, all of the threads
- waiting on the event begin running again. We improved on the
- previous program by replacing the ThreadCount variable idea with
- a couple of events, one for each worker thread. After the main
- thread signals the workers to terminate, it waits on each event
- until the threads set their event.
-
- So far, our main thread has been calling the C library function,
- getch, to retrieve a character from the keyboard. Unfortunately,
- this library function makes a DOS call that polls the BIOS. This
- has the disadvantage that, since each thread gets approximately
- 1/3 of the total CPU time in the system, about 33% of the time
- will be wasted while spinning in the BIOS. We can solve this
- problem by having the main thread poll the keyboard in a loop,
- waiting for a keystroke to occur. After each check, it can call
- the PassTimeSlice function to yield to another thread in the system.
- In this way, the main thread performs the same amount of functional
- work for us but yields better than 95% of the CPU to the workers.
- Here is our improved main function:
-
- VOID main ()
- {
- HANDLE Thread1, Thread2;
- UCHAR ch;
-
- AllocateEvent (&Event1); // allocate events & clear them.
- AllocateEvent (&Event2);
-
- Thread1 = AllocateThread (FirstThread); // start first thread.
- Thread2 = AllocateThread (SecondThread); // start next thread.
-
- while (!kbhit ()) { // check for a keystroke present.
- PassTimeSlice (); // yield to worker threads.
- }
- ch = getch (); // wait for a keypress.
-
- StopThreads = TRUE; // signal threads.
-
- WaitEvent (Event1); // wait for thread 1.
- WaitEvent (Event2); // wait for thread 2.
-
- DeallocateEvent (Event1); // release events to EDOS.
- DeallocateEvent (Event2);
-
- printf ("All worker threads have terminated themselves.\n");
- } // main
-
- We have limited room here for a discussion of all of the
- Embedded DOS kernel objects and their functions. Prioritization
- of threads is a special topic that would require more room than
- we have here, so we urge you to browse the section on real-time
- scheduling in this database for a complete look at how threads
- are used in real-time systems.