home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / C / Snippets / Stuart's Tech Notes / Stu’sThreadUtils / ThreadSynch.h < prev   
Encoding:
Text File  |  1994-12-10  |  8.7 KB  |  197 lines  |  [TEXT/KAHL]

  1. // ThreadSynch.h
  2. // 
  3. // (C) 6th March 1994  Stuart Cheshire <cheshire@cs.stanford.edu>
  4. // 
  5. // This header file defines three synchronization primitives:
  6. // Semaphores, Mutual exclusion locks, and Condition variables.
  7. // They can all be allocated in static storage, dynamic storage,
  8. // or on the stack, and should be initialized (by calling xxxInit)
  9. // before use. (In C++, a constructor would do this automatically.)
  10. //
  11. // If you aren't familiar with Semaphores, Mutual exclusion locks,
  12. // and Condition variables, pick up a good Operating Systems text book.
  13. //
  14. //
  15. // Brief explanation (for those who were sleeping in CS lectures :-)
  16. //
  17. // Mutual exclusion locks are used to prevent two threads accessing the
  18. // same critical piece of code or the same critical piece of data at
  19. // the same time. Put MutexLockAcquire at the start of the critical
  20. // section and MutexLockRelease at the end. If a thread tries to do
  21. // a MutexLockAcquire while another thread is in the critical section,
  22. // it will wait until the other thread has finished before it proceeds.
  23. // It is an error to call MutexLockRelease if you do not hold the lock,
  24. // and it is an error to try to wait for a lock which you are already
  25. // holding yourself ("you" means a particular thread). These errors
  26. // are detected and reported via MacsBug breakpoints.
  27. //
  28. // Condition variables are used when you are waiting for a particular
  29. // condition to become true. The "country bridge" example in test.c
  30. // illustrates this. The easiest way to think of condition variables
  31. // is to consider the following evolution of a program:
  32. //
  33. //        while (!done)
  34. //            {
  35. //            MutexLockAcquire(&lock);
  36. //            if (A && B || C && D && E ...) { do_stuff(); done = TRUE; }
  37. //            MutexLockRelease(&lock);
  38. //            }
  39. //
  40. // The program keeps re-testing its condition until it is satisfactory,
  41. // and this does its stuff. This known as busy-waiting, because the
  42. // program is actively rereading the values of the variables as fast
  43. // as it can, waiting until the variables change to the correct state
  44. // so that it can proceed.
  45. // This could be re-written as follows to eliminate the variable "done":
  46. //
  47. //        MutexLockAcquire(&lock);
  48. //        while (!(A && B || C && D && E ...))
  49. //            { MutexLockRelease(&lock); Pause(); MutexLockAcquire(&lock); }
  50. //        do_stuff();
  51. //        MutexLockRelease(&lock);
  52. //
  53. // Now, while the condition is NOT satisfactory, the program releases
  54. // the lock, pauses for a moment, and then reclaims the lock to re-test
  55. // the condition. The question is, how long should the pause be? If it
  56. // is very short, then the while loop may execute millions of times,
  57. // wasting CPU time and slowing down everything else on the computer,
  58. // including the thing that the while loop is waiting for. If the pause
  59. // is too long, when the condition becomes true, the thread may be in
  60. // the middle of a long pause and it may be a long time before it wakes
  61. // up and "notices" that its condition is OK now. This is where condition
  62. // variables come to the rescue:
  63. //
  64. //        MutexLockAcquire(&lock);
  65. //        while (!(A && B || C && D && E ...)) ConditionVarWait(&cond);
  66. //        do_stuff();
  67. //        MutexLockRelease(&lock);
  68. //
  69. // Now, every time the program does anything that could change the truth
  70. // of the condition (ie changing the value of A, B, C, D or E ...) it is
  71. // required to signal this fact to the condition variable. What the
  72. // ConditionVarWait call does is to release the lock and put the thread
  73. // to sleep, waiting for this signal to arrive. When it arrives, the
  74. // thread wakes up and retests the condition. The ConditionVarWait routine
  75. // reaquires the lock before it returns to the caller, so the single call
  76. // to ConditionVarWait replaces the entire { Release; Pause; Acquire; }
  77. // sequence of the previous example.
  78. //
  79. // The ConditionVarSignal routine wakes up a single waiter, and the
  80. // ConditionVarBroadcast routine wakes up all of them. If you know that
  81. // the change you have made to the condition is such that ANY thread will
  82. // be able to make use of it and proceed, then it is okay to just wake up
  83. // one. If the change to the condition is such that some of the waiting
  84. // threads may be able to make use of it and some may not (as in the
  85. // "country bridge" example) then you MUST use ConditionVarBroadcast
  86. // so that they all get a chance to take a look at the new values. If
  87. // in doubt, use ConditionVarBroadcast exclusively.
  88. //
  89. // Before you can call ConditionVarWait, Signal or Broadcast, you must
  90. // be holding the lock that was associated with the Condition variable
  91. // when it was created (with ConditionVarInit). There are sound reasons
  92. // for this even though they may not always be obvious every time
  93. // (if you have variables that are changed in one thread and tested in
  94. // another, then all accesses to those variables should be protected by
  95. // a mutual exclusion lock). You may be tempted to cut corners with your
  96. // locking in some cases, but it's not worth it. Multi-threaded code is
  97. // hard enough to get right anyway, without inviting extra intermittent
  98. // unrepeatable bugs which are almost impossible to find.
  99. //
  100. // Hint:
  101. // There is no need to try to be to fancy with locking. Just have a small
  102. // number of locks with fairly wide scope -- perhaps even just have a single
  103. // lock with which to synchronize all shared state updates for your entire
  104. // program. Threading is going to give your program such a huge performance
  105. // win anyway that it is hardly worth struggling for that 10% extra
  106. // efficiency if it is going to cause you ten times as many bugs. After
  107. // the program is finished and running reliably you'll have time to
  108. // worry about finding ways to improve performance even more.
  109. //
  110. //
  111. // Semaphores are simple basic counting primitives. It is easiest to think of
  112. // them counting some kind of abstract 'resource' which is in limited supply.
  113. //
  114. // Semaphores are not usually used directly -- they are the basis for
  115. // Mutual exclusion locks and Condition variables described above.
  116. //
  117. // If a buffer has n spaces in it, you can initialize a semaphore to n
  118. // to count the spaces. Each time you want to use a space in the buffer,
  119. // call P(semaphore) and it will decrement the count. If the count is
  120. // already zero, your thread will be stopped until the count increases
  121. // above zero again. When you have finished with your space in the buffer,
  122. // call V(semaphore) to increment the count and indicate that the resource
  123. // is now available for someone else again. If there is another thread
  124. // currently waiting in a P call, it will be woken up so that it can use
  125. // the space which has become available.
  126. //
  127. // If you have a critical section of code, then a semaphore initialized
  128. // to one can act as a mutual exclusion lock (because ONE thread is allowed
  129. // to execute the code at a time).
  130. //
  131. // If you wish to signal from one thread to another, a semaphore initialized
  132. // zero can achieve this (there is initially NO signal, until the signalling
  133. // thread calls V(semaphore) to cause the signal to come into existence).
  134. //
  135. //
  136. // One final note:
  137. // Do NOT forcibly kill a thread with DisposeThread while it it waiting on a
  138. // Semaphore, Mutual exclusion lock, or Condition variable. One deficiency of
  139. // the current Thread Manager is that it has no provision to notify libraries
  140. // (such as this one) when a thread they are managing is destroyed. If you
  141. // destroy a thread that this library thinks it is in control of, then all
  142. // hell will break loose when the deceased thread is woken up again.
  143.  
  144. #ifndef __THREADSYNCH__
  145. #define __THREADSYNCH__
  146.  
  147. #include    <Threads.h>
  148.  
  149. // *****************************************************************
  150. // Semaphore. Operations: Init, Proben (test), Verhogen (increment)
  151.  
  152. typedef struct SemaphoreWaiter SemaphoreWaiter;
  153. typedef struct
  154.     {
  155.     long value;
  156.     SemaphoreWaiter *head;
  157.     SemaphoreWaiter **tail;
  158.     } Semaphore;
  159.  
  160. extern void SemaphoreInit(Semaphore *s, long initialvalue);
  161. extern void SemaphoreP(Semaphore *s);
  162. extern void SemaphoreV(Semaphore *s);
  163.  
  164. #define SemaphoreAcquire SemaphoreP
  165. #define SemaphoreRelease SemaphoreV
  166.  
  167. // *****************************************************************
  168. // MutexLock. Operations: Init, Acquire, Release
  169.  
  170. typedef struct
  171.     {
  172.     Semaphore sem;
  173.     ThreadID lockholder;
  174.     } MutexLock;
  175.  
  176. extern void MutexLockInit(MutexLock *m);
  177. extern void MutexLockAcquire(MutexLock *m);
  178. extern void MutexLockRelease(MutexLock *m);
  179.  
  180. // *****************************************************************
  181. // ConditionVar. Operations: Init, Wait, Signal, Broadcast
  182.  
  183. typedef struct ConditionWaiter ConditionWaiter;
  184. typedef struct
  185.     {
  186.     MutexLock       *lock;
  187.     ConditionWaiter *head;
  188.     ConditionWaiter **tail;
  189.     } ConditionVar;
  190.  
  191. extern void ConditionVarInit(ConditionVar *c, MutexLock *lock);
  192. extern void ConditionVarWait(ConditionVar *c);
  193. extern void ConditionVarSignal(ConditionVar *c);
  194. extern void ConditionVarBroadcast(ConditionVar *c);
  195.  
  196. #endif
  197.