home *** CD-ROM | disk | FTP | other *** search
/ C/C++ User's Journal & Wi…eveloper's Journal Tools / C-C__Users_Journal_and_Windows_Developers_Journal_Tools_1997.iso / sysembed / samples.edh < prev    next >
Encoding:
Text File  |  1995-03-30  |  9.3 KB  |  245 lines

  1. [11. Sample Program Code]
  2.                 A SAMPLE EMBEDDED DOS MULTITHREADED PROGRAM
  3. ════════════════════════════════════════════════════════════════════════
  4. Embedded DOS's kernel API is actually easy to use.  Let's start with a
  5. simple example that does not yet have real-time elements; the "Hello
  6. World" of multithreaded programs.  This program will load, and execute
  7. in the context of its own thread (its "root" thread).  Then it will
  8. allocate two other threads that will busy themselves printing their
  9. own output to the screen.  The output will be intermixed, because the
  10. threads all have the same priority and will each get a bit of CPU time
  11. before the scheduler selects the next thread for execution.
  12.  
  13. #include <kernel.h>
  14. #include <system.h>
  15. #include <conio.h>
  16.  
  17. THREAD FirstThread ()
  18. {
  19.     while (TRUE) {
  20.         putch ('1');            // print a character to the screen.
  21.     }
  22. } // FirstThread
  23.  
  24. THREAD SecondThread ()
  25. {
  26.     while (TRUE) {
  27.         putch ('2');            // print a character to the screen.
  28.     }
  29. } // SecondThread
  30.  
  31. VOID main ()
  32. {
  33.     HANDLE Thread1, Thread2;
  34.     UCHAR ch;
  35.  
  36.     Thread1 = AllocateThread (FirstThread);  // start first thread.
  37.     Thread2 = AllocateThread (SecondThread); // start next thread.
  38.  
  39.     ch = getch ();                           // wait for a keypress.
  40.  
  41.     AbortThread (Thread1);                   // stop first thread.
  42.     AbortThread (Thread2);                   // stop second thread.
  43.     printf ("All worker threads have been aborted.\n");
  44. } // main
  45.  
  46. This example illustrates several of the problems that must be addressed
  47. when writing multithreaded programs.  Let's discuss them, one by one.
  48.  
  49. First, you'll notice there are three threads in this system.  The first
  50. one executes the main function.  This thread gets created automatically
  51. by Embedded DOS when the program is loaded.  The other two threads are
  52. created by the first thread in the calls to the AllocateThread kernel
  53. function.  In each case, the address of the function to be executed by
  54. the new thread is passed to AllocateThread by simply calling it by name.
  55.  
  56. Next, after the two additional threads are created, they begin running
  57. in the system, independent from the root thread that executed the main
  58. function.  These threads will continue to run in the system until they
  59. are aborted by the main thread with the calls to Abort Thread.  Threads
  60. may also deallocate themselves by calling DeallocateThread.
  61.  
  62. There is a problem with calling AbortThread on these threads, however.
  63. You'll note that each thread calls the C library function putch to write
  64. to STDOUT.  This library function makes a DOS call, and Embedded DOS
  65. automatically allocates a stack for purposes of handling the call
  66. reentrantly.  Because the main program's AbortThread call could destroy
  67. the thread while the internal resources remain allocated, it is possible
  68. that these resources will never be cleaned up.
  69.  
  70. We'll solve this problem in the next example.  A better approach to
  71. destruction of threads is slightly more complicated, but necessary.
  72. We will define a global variable that the main function can set to
  73. TRUE when it wants to signal the threads to kill themselves, at the
  74. next safe opportunity.  Here it is, our better version of the program:
  75.  
  76. #include <kernel.h>
  77. #include <system.h>
  78. #include <conio.h>
  79.  
  80. static USHORT StopThreads=FALSE;
  81. static USHORT ThreadCount=0;
  82.  
  83. THREAD FirstThread ()
  84. {
  85.     ThreadCount++;
  86.     while (!StopThreads) {
  87.         putch ('1');            // print a character to the screen.
  88.     }
  89.     ThreadCount--;
  90.     DeallocateThread ();        // terminate this thread.
  91. } // FirstThread
  92.  
  93. THREAD SecondThread ()
  94. {
  95.     ThreadCount++;
  96.     while (!StopThreads) {
  97.         putch ('2');            // print a character to the screen.
  98.     }
  99.     ThreadCount--;
  100.     DeallocateThread ();        // terminate this thread.
  101. } // SecondThread
  102.  
  103. VOID main ()
  104. {
  105.     HANDLE Thread1, Thread2;
  106.     UCHAR ch;
  107.  
  108.     Thread1 = AllocateThread (FirstThread);  // start first thread.
  109.     Thread2 = AllocateThread (SecondThread); // start next thread.
  110.  
  111.     ch = getch ();                           // wait for a keypress.
  112.  
  113.     StopThreads = TRUE;                      // signal threads.
  114.  
  115.     while (ThreadCount > 0) ;                // spin waiting for threads.
  116.  
  117.     printf ("All worker threads have terminated themselves.\n");
  118. } // main
  119.  
  120. In the above example, we also maintained a counter of the number of
  121. threads in the system.  When the child threads enter the system, they
  122. increment the counter.  They decrement the counter before they
  123. terminate, making it easy for the main function to know when they
  124. have terminated themselves.
  125.  
  126. We can still improve on this, for we should realize that on
  127. occasion, we can't realy on the C compiler to generate code that
  128. is multitasking- safe, or MT-safe.  Can you spot the problem?
  129. It is the use of the autoincrement and autodecrement operators
  130. on the ThreadCount variable. While setting the StopThreads
  131. variable is MT-safe, the increment and decrement operators are
  132. potential hazards in real-time code because the C compiler may
  133. generate multiple instructions for them that could potentially
  134. be interrupted by context switches.  We could use features of
  135. Embedded DOS to temporarily suspend context switching around these
  136. operators, but a much more elegant solution to the problem, and one
  137. that shows how kernel events are used, is shown below:
  138.  
  139. #include <kernel.h>
  140. #include <system.h>
  141. #include <conio.h>
  142.  
  143. static USHORT StopThreads=FALSE;
  144. static HANDLE Event1, Event2;
  145.  
  146. THREAD FirstThread ()
  147. {
  148.     while (!StopThreads) {
  149.         putch ('1');            // print a character to the screen.
  150.     }
  151.     SetEvent (Event1);          // tell root thread we're done.
  152.     DeallocateThread ();        // terminate this thread.
  153. } // FirstThread
  154.  
  155. THREAD SecondThread ()
  156. {
  157.     while (!StopThreads) {
  158.         putch ('2');            // print a character to the screen.
  159.     }
  160.     SetEvent (Event2);          // tell root thread we're done.
  161.     DeallocateThread ();        // terminate this thread.
  162. } // SecondThread
  163.  
  164. VOID main ()
  165. {
  166.     HANDLE Thread1, Thread2;
  167.     UCHAR ch;
  168.  
  169.     AllocateEvent (&Event1);    // allocate events & clear them.
  170.     AllocateEvent (&Event2);
  171.  
  172.     Thread1 = AllocateThread (FirstThread);  // start first thread.
  173.     Thread2 = AllocateThread (SecondThread); // start next thread.
  174.  
  175.     ch = getch ();              // wait for a keypress.
  176.  
  177.     StopThreads = TRUE;         // signal threads.
  178.  
  179.     WaitEvent (Event1);         // wait for thread 1.
  180.     WaitEvent (Event2);         // wait for thread 2.
  181.  
  182.     DeallocateEvent (Event1);   // release events to EDOS.
  183.     DeallocateEvent (Event2);
  184.  
  185.     printf ("All worker threads have terminated themselves.\n");
  186. } // main
  187.  
  188. We solved this problem by using MT-safe Embedded DOS kernel
  189. objects called events to perform the synchronization.  Event
  190. objects have a state; they are either in the set state or in the
  191. cleared state. Events can be set, cleared, pulsed, or waited on
  192. by any number of threads.  When an event is set, or momentarily
  193. set by calling the PulseEvent function, all of the threads
  194. waiting on the event begin running again.  We improved on the
  195. previous program by replacing the ThreadCount variable idea with
  196. a couple of events, one for each worker thread.  After the main
  197. thread signals the workers to terminate, it waits on each event
  198. until the threads set their event.
  199.  
  200. So far, our main thread has been calling the C library function,
  201. getch, to retrieve a character from the keyboard.  Unfortunately,
  202. this library function makes a DOS call that polls the BIOS.  This
  203. has the disadvantage that, since each thread gets approximately
  204. 1/3 of the total CPU time in the system, about 33% of the time
  205. will be wasted while spinning in the BIOS.  We can solve this
  206. problem by having the main thread poll the keyboard in a loop,
  207. waiting for a keystroke to occur.  After each check, it can call
  208. the PassTimeSlice function to yield to another thread in the system.
  209. In this way, the main thread performs the same amount of functional
  210. work for us but yields better than 95% of the CPU to the workers.
  211. Here is our improved main function:
  212.  
  213. VOID main ()
  214. {
  215.     HANDLE Thread1, Thread2;
  216.     UCHAR ch;
  217.  
  218.     AllocateEvent (&Event1);    // allocate events & clear them.
  219.     AllocateEvent (&Event2);
  220.  
  221.     Thread1 = AllocateThread (FirstThread);  // start first thread.
  222.     Thread2 = AllocateThread (SecondThread); // start next thread.
  223.  
  224.     while (!kbhit ()) {         // check for a keystroke present.
  225.         PassTimeSlice ();       // yield to worker threads.
  226.     }
  227.     ch = getch ();              // wait for a keypress.
  228.  
  229.     StopThreads = TRUE;         // signal threads.
  230.  
  231.     WaitEvent (Event1);         // wait for thread 1.
  232.     WaitEvent (Event2);         // wait for thread 2.
  233.  
  234.     DeallocateEvent (Event1);   // release events to EDOS.
  235.     DeallocateEvent (Event2);
  236.  
  237.     printf ("All worker threads have terminated themselves.\n");
  238. } // main
  239.  
  240. We have limited room here for a discussion of all of the
  241. Embedded DOS kernel objects and their functions.  Prioritization
  242. of threads is a special topic that would require more room than
  243. we have here, so we urge you to browse the section on real-time
  244. scheduling in this database for a complete look at how threads
  245. are used in real-time systems.