It is rarely a good 
idea to lower a 
thread's base priority 
number.
Multiple-Use 
Semaphores

By specifying an 
initial value of two or 
more, you can set the 
number of threads 
that can access a 
semaphore at the 
same time. This is 
often used to track 
multiple instances of 
a particular resource.
Figure B-0

Display B-0

SwatDisplay B-0

Table B-0
B

BThreads and Semaphores

Revision:   

One of the most impressive features of GEOS is its ability to perform 
several tasks simultaneously, even on the least powerful PCs. Of course, the 
PC has only one processor and can execute only one instruction at a time. 
GEOS keeps track of the various tasks, or threads, that are underway, and 
by switching from one thread to another many times per second creates the 
illusion that the PC is actually doing all the jobs at the same time.

This chapter covers

u	the basic concepts of multitasking,

u	the GEOS multitasking scheme,

u	the steps to creating a multi-threaded application, and

u	the use of semaphores to synchronize threads and avoid deadlock.

You will need to know the information in this appendix if you will be 
writing a multi-threaded application. The information in the appendix is 
not essential if your application will be single-threaded or if you will use 
the standard GEOS dual-thread architecture. For a dual-thread 
application, be careful not to send a message with @call from an object run 
by a user interface thread to an object run by any other thread of the 
application.

	B.1	Multitasking Goals

The GEOS multitasking system is one of the most sophisticated available 
for PCs today. It was designed with the latest available technology and was 
created to serve the following two primary goals:

u	Fast Response
The GEOS multitasking system was designed with a strong emphasis 
on rapid response to user input. Prompt, visible reaction to user action 
is the single most important factor contributing to the perception of 
speed. For example, if a user changes an element in a spreadsheet 
(requiring the whole spreadsheet to be recomputed) and then pulls 
down a menu, he wants the menu to appear right away. If the menu 
does not appear until the computation is finished, the system will seem 
sluggish. If the computation takes a few seconds or longer, the user 
may wonder whether the system has crashed altogether.

u	Ease of Programming
Making the programmer's job easy is another motive behind the design 
of GEOS multitasking. Ideally, the programmer should not need to be 
aware that his program will run in a multitasking environment. The 
program should proceed as though it were the only one running on the 
system. Besides certain "good citizen" rules, application programs are 
isolated from the multitasking environment. On the other hand, if a 
programmer wants to take advantage of the multitasking capability of 
GEOS (by designing a program to perform more than one task 
concurrently) the system is designed to make this as simple and 
efficient as possible.

	B.2	Two Models of Multitasking

Many operating systems provide the ability to carry out multiple tasks at 
the same time. While each operating system has its own unique way of 
managing this, there are two fundamental types of multitasking: 
cooperative (used by Microsoft Windows), and preemptive (used by GEOS).

	B.2.1	Cooperative Multitasking

A cooperative multitasking system, as the name suggests, is one in which 
the various programs cooperate. They agree to share the system and its 
resources. Each program running under a system has complete control 
while it is actually running. Every so often, when it reaches a convenient 
place, it calls a special system routine (called a context-switch routine) to 
see if any other program has work do to. If so, that program takes control 
of the system until it in turn reaches a convenient stopping point and 
passes control on to the next waiting program. If several programs are 
ready to run, they wait in a queue so that each one gets a chance to run 
before the first program runs again. (It is also possible to implement a 
cooperative multitasking system where some programs have a higher 
priority than others. In this case, a more complicated algorithm might be 
used by the context-switch routine to determine which program gets to run 
next.)

Smooth operation of a cooperative multitasking system requires that all 
programs be written to call the context-switch routine frequently. When 
large calculations are being performed, programmers tend to find this 
requirement inconvenient. Writing well-behaved programs (i.e., programs 
that do not keep control of the processor for too long at a stretch) is 
especially difficult because most cooperative multitasking systems impose 
restrictions on when a context switch can take place.

	B.2.2	Preemptive Multitasking

In a preemptive multitasking system, programs do not have to relinquish 
control of the system voluntarily. Instead of calling a context-switch 
routine, the program is written as though it were going to run continuously 
from start to finish. The hardware generates a timer interrupt a number of 
times each second, and that interrupt triggers the kernel's context-switch 
mechanism.

The context switch can also be triggered by other interrupts. For example, 
if the user moves the mouse in GEOS, the mouse will generate an interrupt. 
GEOS responds by marking the input thread runnable; the thread will then 
run after the interrupt is complete. This is how GEOS achieves its 
extraordinary response times to user input.

With preemptive multitasking, each program can have the illusion that it 
is running continuously and has complete control of the system. It also 
enables the system to interact quickly with the user even when 
applications are busily computing new results.

For example, a spreadsheet program can keep running until the timer 
interrupt causes a context switch. Other programs, including the one 
responsible for drawing menus, then get their turns to run. If a user clicks 
on a pull-down menu, the menu will appear. When the spreadsheet 
program regains control of the system, it can carry on from where it was 
interrupted, blissfully unaware that any of this has taken place.

While preemptive multitasking makes most things simpler for the user 
and application programmer, there are a few important issues to consider 
in writing programs for a preemptive multitasking system such as GEOS. 
When the context switches are controlled by a timer interrupt, they can 
occur between any two instructions. If a program is interrupted while it is 
updating a data structure, that data structure may be left in an 
inconsistent state while another thread is running. If the data structure is 
not accessed by any other process running on the system, there is no 
problem: the update will be completed when the program resumes. 
However, some data structures (including system resources) may be 
accessed by more than one program. It is important that two updates to the 
same data do not happen at the same time.

This problem is analogous to one often experienced by network users. If a 
text file is being edited at the same time by two different users and they 
both save their changes to the file, whoever saves first will have his version 
overwritten by the other. Many systems have a means of locking a file while 
you are editing it; no one else can begin editing the file while you have it 
locked. A preemptive multitasking system must have a similar locking 
scheme to prevent two accesses to the same data structure from happening 
at the same time. The locking mechanism should be as transparent as 
possible to the programmer. For example, the locking and unlocking of 
system resources should happen automatically so that application 
programmers need not concern themselves with it.

This is exactly how GEOS coordinates its resources, as you shall see in the 
following sections.

	B.3	GEOS Multitasking

GEOS implements a preemptive multitasking scheme. Application 
programs are required to follow certain "good citizen" rules and are 
otherwise given the illusion that they are alone on the system. The 
applications themselves are, for the most part, isolated from the 
multitasking environment.

	B.3.1	GEOS Threads

The various units that take turns running in the system are called 
"threads" in GEOS. Threads can have different priorities; a thread that has 
a higher priority (indicated by a lower priority number) will generally get 
more processor time.

	B.3.1.1	Keeping Track of Threads

In order to switch among threads, the system needs to keep track of certain 
things about each one. For each thread, the system keeps track of priority, 
the most recent values of the registers, and flags. The priorities are used to 
determine which thread is going to run next. When the thread is run, the 
appropriate registers are reset to the values they had the last time the 
thread was stopped. This allows the thread to resume execution as though 
it had never been interrupted. 

Like other things in GEOS, each thread has a handle, a sixteen-bit value 
which programs use to refer to the thread. When calling thread-related 
routines (e.g., to set the thread's priority), programs use the handle to 
specify the thread.

	B.3.1.2	Event-Driven and Procedural Threads

GEOS uses two different types of threads. The two types differ only in the 
way they run when their turn comes and in the way they are created. The 
discussions about priority, context switches, and synchronization 
elsewhere in this chapter apply equally to both types.

The first type of GEOS thread is an "event-driven" thread. An event-driven 
thread normally executes code for one or more objects. Each event-driven 
thread has an event queue; when a message is sent to any object created by 
the thread, the message is placed in the thread's event queue. The thread 
processes each event in the order received by executing the appropriate 
message handler from the object's class definition.

Messages can also be sent to the thread itself rather than to an object 
created by the thread. When the thread is created, it is assigned a 
class-normally a subclass of ProcessClass (for non-application threads) 
or GenProcessClass (for application threads)-to determine the handlers 
to use when messages are sent directly to the thread. In this sense, the 
thread can be considered an instance of the given class.

The second type of thread is procedural. Rather than running handlers for 
messages in an object-oriented scheme, it simply executes procedural code. 
The system does not provide an event queue for a procedural thread, and 
messages cannot be sent to such a thread.

	B.3.2	Context Switches

Context switches are triggered in two ways under GEOS. The first is a timer 
or other hardware interrupt. The second occurs when the thread reaches a 
point where it cannot continue right away, such as when the thread exits 
or when it attempts to access a locked resource.

The PC hardware generates a timer interrupt sixty times per second. The 
time between timer interrupts is called a tick. Each thread is allowed to run 
for a specified number of ticks before the timer interrupt routine will 
transfer control to a different thread. This number of ticks is the same for 
all threads; it is called a time slice.

When a thread begins its turn, GEOS sets a counter to the number of ticks 
in a time slice. At each timer interrupt, GEOS decrements the counter. If 
the counter has not reached zero, control is immediately returned to the 
running thread. When the counter reaches zero, GEOS checks to see if some 
other thread has reached a higher priority than the current thread. If so, 
the current thread is placed in the system's list of runnable threads (called 
the run queue), and the highest priority thread begins running. Otherwise 
the current thread gets to run for another time slice.

Sometimes a thread will try to access a system resource (or shared data 
object) which is currently in use by another thread. When this happens, the 
thread must wait until the desired resource is available. The thread is 
placed on a queue, and a new thread is selected from the run queue. Every 
thread that is not currently running is either in the run queue (waiting to 
be executed) or in another queue waiting for a needed resource to become 
available. When the resource becomes available, the thread is moved to the 
run queue and is ready to be run again. This process is described in greater 
detail in section l B.5 on page B-926.

	B.3.3	Thread Scheduling

When there is a context switch, GEOS must determine which thread will be 
executed next. It does this by examining the run queue and selecting the 
most important thread to run. A thread's importance is reflected in its 
priority: the lower the priority number, the more important the thread.

	B.3.3.1	Base Priority

Each thread gets a base priority, a value from zero to 255. Applications 
generally have a priority between 128 and 191. Threads that are critical to 
quick system response (such as user input threads that manage such 
things as pull-down menus and dialog boxes) are given lower numbers. 
Higher numbers can be given to less time-critical threads such as those 
used for print spooling and other background activities. To provide faster 
response to the user, GEOS temporarily reduces an application's base 
priority by 32 (giving it a higher priority) when the user is interacting with 
it. When the user switches to interact with another application, the first 
application's base priority returns to normal, and the new one gets a 
reduced base priority number.

	B.3.3.2	Recent CPU Usage

GEOS keeps track of a thread's recent CPU usage with a number that varies 
from zero to 60. Starting at zero, the number is incremented at every timer 
interrupt while the thread is running. Once each second, the recent CPU 
usage is halved, so that as a thread's CPU usage recedes into the past, its 
recent CPU usage number will diminish. The resulting number reflects how 
much time the thread has had control of the CPU and how long ago this 
time was.

	B.3.3.3	Current Priority

Once each second, the current priority of each thread is recomputed by 
adding the base priority to the recent CPU usage. The resulting number is 
the one used in selecting a thread from the run queue.

When it is time for a context switch, GEOS selects the thread with the 
lowest current priority number from the run queue. If there is a tie, the 
selection is arbitrary. However, because recent CPU usage counts against a 
thread, two threads of equal priority will not stay that way. One will run, 
and its recent CPU usage (and thus its current priority number) will be 
increased. The other thread will therefore get its chance to run.

	B.3.4	Applications and Threads

There are two standard architectures for GEOS applications: single-thread 
and dual-thread. While the single-thread option is somewhat easier to 
program, there are distinct advantages to the dual-thread method.

In the dual-thread architecture, one thread manages the application's user 
interface while the other manages the rest of the application's 
functionality. Since both threads are event-driven, each has an event 
queue. Messages that are sent to user interface objects (resulting from 
mouse clicks, keyboard input, etc.) can be handled without waiting for 
other tasks in the application to be completed. This allows the application 
to respond to user input (by putting up menus, moving windows, and so on) 
without first completing the current non-user-interface task (which may 
involve a lot of computation). 

The dual-thread architecture, however, poses a problem of 
synchronization: One thread can get ahead of the other. Threads that count 
on each other must keep track of each other's progress in order to avoid 
this; when potential problems are identified, use semaphores to keep the 
threads in line (see section l B.5 on page B-926).

	B.4	Using Multiple Threads

It is possible for an application to create additional threads for a variety of 
purposes. For example, a terminal emulation program might have a thread 
whose sole purpose is to monitor the serial line for incoming characters. 
This might avert the danger of a serial input buffer or stream overflowing 
while the application is performing an involved task, such as loading a text 
file from disk, while not requiring a great deal of fixed memory for the serial 
driver's input buffer.

	B.4.1	How GEOS Threads Are Created

GEOS threads can be created in three different ways. The first thread (or 
pair of threads, in the dual-thread architecture) for each application is 
created automatically when the application is launched. By calling the 
appropriate routines, the application can create additional threads to 
handle messages sent to certain objects or to run procedural code.

	B.4.1.1	The Application's Primary Thread

The application's primary thread is created automatically by GEOS when 
the application is launched. (See "Applications and Geodes," Chapter 6 for 
information on launching applications.) For example, if a user 
double-clicks on your application's icon in a GeoManager window, 
GeoManager calls the library routine UserLoadApplication(), specifying 
the geode file and certain other parameters. This calls the GeodeLoad() 
routine in the GEOS kernel.

If the program is written using the single-thread model, GEOS creates an 
event-driven thread to handle messages sent to any object in the program. 
If the program is written using the dual-thread model, GEOS creates one 
event-driven thread to handle messages sent to the program's user 
interface objects and another to handle messages sent to other objects in 
the program.

If the program requires more than two threads, the extra thread(s) must 
be allocated manually on startup and destroyed before the application exits 
completely.

	B.4.1.2	Event-Driven Threads

ThreadAttachToQueue()

To create an event-driven thread (one that handles messages sent to 
certain objects), send a MSG_PROCESS_CREATE_EVENT_THREAD to your 
application's primary thread, passing as arguments the object class for the 
new thread (a sublass of ProcessClass) and the stack size for the new 
thread (1 K bytes is usually a good value, or around 3 K bytes for threads 
that will handle keyboard navigation or manage a text object). This 
message is detailed in "System Classes," Chapter 1 of the Object Reference 
Book.

GEOS will create the new thread, give it an event queue, and send it a 
MSG_META_ATTACH. Initially, the thread will handle only messages sent 
to the thread itself. If the thread creates any new objects, however, it will 
handle messages sent to those objects as well. To control the behavior of the 
new thread, define a subclass of ProcessClass and a new handler for 
MSG_META_ATTACH. The new handler can create objects or perform 
whatever task is needed. Be sure to start your new handler with 
@callsuper() so that the predefined initializations are done as well.

If you have a thread that you want attached to a different event queue, you 
can use ThreadAttachToQueue(). This routine is not widely used except 
when applications are shutting down and objects need to continue handling 
messages while not returning anything. It's unlikely you will ever use this 
routine.

	B.4.1.3	Threads That Run Procedural Code

ThreadCreate()

To create a thread to run procedural code, first load the initial function into 
fixed memory. Then call the system routine ThreadCreate(), passing the 
following arguments: The base priority for the new thread, an optional 
sixteen-bit argument to pass to the new thread, the entry point for the code, 
the amount of stack space GEOS should allocate for the new thread, and the 
owner of the new thread.

	B.4.2	Managing Priority Values

ThreadGetInfo(), ThreadModify(), ThreadGetInfoType

You can ascertain and modify the priority of any thread in the system, 
given the thread's handle. The handle is provided by the routines that 
create threads, and it can be provided by one thread to another in a 
message. The following system routines relate to the priority of a thread:

ThreadGetInfo() returns information about a thread. When calling 
ThreadGetInfo(), pass the handle of the thread in question and a value 
of the type ThreadGetInfoType (see below). If zero is passed as the 
thread handle, ThreadGetInfo() returns information on whatever thread 
executed the call.

ThreadGetInfoType is an enumerated type with three possible values:

TGIT_PRIORITY_AND_USAGE
This requests the base priority and recent CPU usage of a 
thread. (To determine the current priority, simply add the 
base priority to the recent CPU usage.)

TGIT_THREAD_HANDLE
This requests the handle of the thread. Use this (with a 
thread handle of zero) to get the caller's own thread handle.

TGIT_QUEUE_HANDLE
This requests the handle of the event queue for an 
event-driven thread. It returns a zero handle if the thread is 
not event-driven.

ThreadModify() changes the priority of a thread. The arguments to pass 
include the handle of the thread to modify (zero for the thread executing 
the call), a new base priority for the thread, and two flags: One that 
indicates whether to change the thread's base priority and one that 
indicates whether to reset the thread's recent CPU usage to zero. If the flag 
to change the thread's base priority is not set, the new base priority 
argument is ignored. In general, you should only lower a thread's priority 
(i.e., raise its base priority number). Applications that raise their own 
priority damage the performance of the system as a whole. Keep in mind 
that GEOS already favors the thread with which the user is interacting.

There are several pre-defined priority levels you can use to set a thread's 
priority. You may wish to use these when debugging to raise the priority of 
a potentially buggy thread for efficient debugging. These are listed below, 
each a different constant.

u	PRIORITY_TIME_CRITICAL
Threads should not be set time-critical unless they must own the 
processor exclusively for a certain amount of time. Excluding other 
threads can have undesirable side effects.

u	PRIORITY_HIGH 

u	PRIORITY_UI 

u	PRIORITY_FOCUS 

u	PRIORITY_STANDARD 

u	PRIORITY_LOW 

u	PRIORITY_LOWEST 

	B.4.3	Handling Errors in a Thread

ThreadHandleException(), ThreadException

Some threads in GEOS will want to handle certain errors in special ways. 
The errors a particular thread can intercept and handle are listed in an 
enumerated type called ThreadException, the elements of which are 
shown below:

u	TE_DIVIDE_BY_ZERO

u	TE_OVERFLOW

u	TE_BOUND

u	TE_FPU_EXCEPTION

u	TE_SINGLE_STEP

u	TE_BREAKPOINT

A thread can handle a particular exception by setting up a special handler 
routine and calling ThreadHandleException() when one of these 
exceptions occurs. This is useful if a number of objects are run by the same 
thread and all should handle a particular exception in the same way; the 
routine can be thread-specific rather than object-specific. 
ThreadHandleException() must be passed the thread's handle, the 
exception type, and a pointer to the handler routine's entry point.

	B.4.4	When a Thread Is Finished

ThreadDestroy()

Whenever an application creates an additional thread with 
MSG_PROCESS_CREATE_EVENT_THREAD or ThreadCreate(), it must be 
sure that the thread exits when it is finished. Simply exiting the 
application may not eliminate any additional threads, and these threads 
can cause GEOS to hang when shutting down the system.

When a thread exits, it should first release any semaphores or thread locks 
it has locked and free any memory or other resources that are no longer 
needed. Resources in memory do not have to be freed in the same thread 
that allocated them, but you should be sure that they are freed before the 
application exits.

A procedural thread exits by calling ThreadDestroy() with two 
arguments: an error code and an optr. When the thread exits, it sends (as 
its last act) a MSG_PROCESS_NOTIFY_THREAD_EXIT to the application's 
primary thread and a MSG_META_ACK to the object descriptor passed. 
Each message has the error code as an argument. In designing a 
multi-threaded application, you can create methods for 
MSG_PROCESS_NOTIFY_THREAD_EXIT (in your primary thread's class) or 
MSG_META_ACK (in any class) for communication among threads, and you 
may use the error code for any data you choose. The convention is that an 
error code of zero represents successful completion of a thread's task.

An event-driven thread should not call ThreadDestroy() directly because 
its event queue must be removed from the system cleanly. Instead, send a 
MSG_META_DETACH to the thread, passing the same arguments as for 
ThreadDestroy(). The handler for MSG_META_DETACH in MetaClass 
cleanly removes the event queue and terminates the thread, sending the 
same messages as described above. You may write a special handler for 
MSG_META_DETACH when you subclass ProcessClass, but be sure to end 
the handler with @callsuper() so the thread exits properly.

	B.5	Synchronizing Threads

Because GEOS is a preemptive multitasking environment, it needs a way 
to prevent two threads from accessing system resources (or other shared 
data) simultaneously. Otherwise, data could be corrupted. GEOS uses 
semaphores to prevent to threads from performing conflicting operations at 
the same time.

	B.5.1	Semaphores: The Concept

A semaphore is a data structure on which three basic operations are 
performed. These operations allow threads to avoid conflicting with other 
threads. Think of a semaphore as a flag which programs can set to indicate 
that some resource is locked. Anyone else who wants to use the resource 
must wait in line until whoever set the flag resets it. The three basic 
operations on a semaphore are initialize, set, and reset.

	B.5.1.1	Initialize

The "initialize" operation creates a semaphore and gives it a name. In its 
initial state, the semaphore is "unlocked," meaning the first process that 
attempts to access it will succeed right away. A semaphore must be 
initialized before it can be used, although the initialization can be handled 
by the operating system so that it is transparent to the programmer.

	B.5.1.2	Set (the "P" Operation)

The "P" operation is what a program performs in order to make sure it is 
allowed to proceed. For example, if the program is about to access shared 
data, it performs the "P" operation on the semaphore protecting that data 
to make sure no other program is accessing it.

If the semaphore is unlocked and a thread performs the "P" operation on it, 
the thread simply marks the semaphore locked and proceeds normally. If 
the semaphore is locked, a thread performing the "P" operation will block. 
This means the thread will stop running and will wait in the thread queue 
associated with the semaphore. When its turn to perform the protected 
operation arrives, the thread will proceed.

	B.5.1.3	Reset (the "V" Operation)

When a thread has finished a protected operation, it performs the "V" 
operation to unlock the semaphore. If there are other threads in the queue 
for this semaphore, one of them is restarted and takes over, keeping the 
semaphore locked. Thus only one thread at a time runs the protected 
operation. If a thread performs the "V" operation on a semaphore and there 
are no other threads waiting in the semaphore's queue, the thread simply 
marks the semaphore unlocked and proceeds.

Programs must always reset a semaphore when they are done with it. If 
you fail to reset a semaphore, other threads may wait forever.

Because only one thread at a time is performing the protected operation 
and this thread is responsible for unlocking the semaphore, it is sometimes 
said to "have" the semaphore. The "P" and "V" operations are often referred 
to as "grabbing" and "releasing" a semaphore, respectively.

	B.5.1.4	The Dreaded Deadlock Problem

When semaphores are not used carefully, they can cause programs to stop 
running entirely. Suppose Thread A tries to grab a semaphore which 
Thread B has locked. Thread A stops running until Thread B releases the 
semaphore. Thread B then tries to grab a second semaphore, which Thread 
A has previously locked. Thread B waits for Thread A to release the second 
semaphore, but Thread A is waiting for Thread B to release the first 
semaphore. Neither thread will ever wake up. This situation is called 
"deadlock."

To avoid deadlock, follow these guidelines:

u	When possible, avoid having a thread attempt to lock one semaphore 
while it already has another one locked.

u	When two or more semaphores may be locked by the same thread at the 
same time, they should always be used in the same order. Semaphores 
are often arranged in a hierarchy, with the coarsest (the one controlling 
access to the most resources) at the top and the finest at the bottom. 
Any thread grabbing multiple semaphores in this hierarchy must 
always grab from top to bottom; that is, no thread should grab a 
semaphore "above" one it already has locked.

u	In certain situations, a semaphore is used "between" two threads. One 
thread needs to wait until another performs a specific action. The first 
thread is said to "block" on the second. Of course, two threads must 
never block on each other. To ensure this situation never arises, only 
one of the threads should use the @call keyword when sending 
messages to the other; the other should always use @send and, when 
getting return information, have some sort of notification message sent 
in response.

u	When using the GEOS messaging system to send a message with @call 
to an object in another thread, the sending thread automatically blocks 
on the receiving thread. Since a number of user interface objects must 
be sent messages with @call, the application thread sometimes blocks 
on the user interface thread. To avoid deadlock, code that runs in the 
user interface thread must never send messages with @call to objects 
in the application thread. (This is a particular example of the above 
rule being implemented.)

	B.5.2	Semaphores In GEOS

GEOS contains several system resources that are protected by semaphores. 
Since application programs can access these resources only through library 
routines, the programmer does not need to be aware of these semaphores; 
the required operations are performed by the library routines. For system 
resources (e.g. files, memory, handles), GEOS defines semaphores and 
provides special routines to set and reset them. The chapter that describes 
each system resource explains how to use the special semaphores that 
protect the resource.

The routines described in this section illustrate GEOS semaphores and can 
be used to create semaphores to protect resources defined within a 
multithreaded application. There are routines for each of the operations 
(initialization, P, and V), and there are special routines which simplify the 
use of a semaphore by multiple objects within the same thread.

Note that it is possible to create a semaphore with a starting value greater 
than one. That is, you can create a semaphore that will allow more than one 
thread to grab it at once. Typically, if only one thread may grab the 
semaphore, the thread is called a "mutual exclusion," or "mutex," 
semaphore, because it is normally used to make sure two threads don't 
mutually grab a particular resource.

Semaphores that can be grabbed by more than one thread are generally 
called "scheduling semaphores" because they allow easy manipulation of 
scheduled resources. The classic example of this is the "producer-consumer 
problem" wherein one thread produces buffers and another consumes 
them. Initially, no buffers exist, so the semaphore starts at zero. The 
consumer goes into a loop wherein it simply P's the semaphore (blocking 
until a buffer exists), takes the first buffer in the queue, processes the 
buffer, destroys the buffer, and then returns to the top of the loop. The 
producer, meanwhile, can produce any number of buffers, queue them, and 
V the semaphore once for each consumable buffer. The consumer thread 
will continue to process until all the buffers are consumed, and then it will 
block and wait for more buffers.

	B.5.2.1	Operations on a Semaphore

ThreadAllocSem(), ThreadPSem(), ThreadPTimedSem(), 
ThreadVSem(), ThreadFreeSem()

To create a semaphore, simply call the routine ThreadAllocSem(), 
passing an initial value for the semaphore. This should normally be one to 
indicate the semaphore is unlocked. If you want the semaphore to be locked 
initially, pass an initial value of zero. In either case, the returned value will 
be the handle of the newly created semaphore. Use this handle with the 
routines described below.

Once a semaphore is created, a thread can lock it (i.e., perform the "P" 
operation) by calling the routine ThreadPSem(), passing the semaphore's 
handle as an argument. If the semaphore is unlocked, the thread locks it 
and proceeds; otherwise the thread waits in the semaphore's thread queue.

Another routine that performs the "P" operation is ThreadPTimedSem(). 
When calling this routine, pass as arguments the semaphore's handle and 
an integer representing a number of ticks. This integer is a time limit: If 
another thread has the semaphore locked and does not unlock it within the 
specified number of ticks, the routine will return with a flag indicating the 
lock was unsuccessful. Programs that use ThreadPTimedSem() must 
check this flag and must not perform the protected operation if it is set. The 
most common use of this routine is with a time limit of zero, meaning that 
the semaphore should be locked only if it is available right away-if it is 
not available, the thread will continue with some other action.

To release the semaphore (by performing the "V" operation), the thread 
calls ThreadVSem(), again passing the semaphore's handle. If there are 
other threads waiting for the semaphore, the one with the lowest current 
priority number takes over.

When a semaphore is no longer needed, it can be destroyed by calling 
ThreadFreeSem() with the semaphore's handle as an argument.

	B.5.2.2	Operations on a Thread Lock

ThreadAllocThreadLock(), ThreadGrabThreadLock(), 
ThreadReleaseThreadLock(), ThreadFreeThreadLock()

At times it is convenient to have a program lock a semaphore that it has 
already locked. For example, one routine might lock a semaphore 
protecting a piece of memory and then call itself recursively. A thread lock 
is a semaphore that can be locked any number of times, as long as each lock 
is performed by the same thread. If another thread tries to grab the thread 
lock, it will wait until the first thread has performed the "V" operation once 
for each time it has run the "P" operation. It is possible to write reentrant 
routines using thread locks but not using regular semaphores.

A thread lock is initialized with the ThreadAllocThreadLock() routine, 
which takes no arguments. A thread lock is always created unlocked. To 
perform the "P" and "V" operation on a thread lock, use 
ThreadGrabThreadLock() and ThreadReleaseThreadLock(), 
respectively, and pass the semaphore's handle as an argument. These 
routines are analogous to ThreadPSem() and ThreadVSem() for 
semaphores. When a thread lock is no longer needed, it should be freed 
with a call to ThreadFreeThreadLock().
Avoid deadlock!

Be very careful when 
using multiple 
semaphores.
