
<PRE>

Mach C Threads Package

Mach provides a set of low-level, language-independent functions for 
manipulating threads of control.  The C Threads package is a run-time 
library that provides a C language interface to these facilities.  The 
constructs provided are:

·	Forking and joining of threads 
·	Protection of critical regions with mutex variables 
·	Condition variables for synchronization of threads

If you intend to do multithreaded applications, you should use the C 
threads routines rather than the Mach system calls.  The C threads 
package provides a natural and efficient set of functions for 
multithreaded applications, whereas the thread kernel calls are 
designed to provide the low-level mechanisms that packages such as 
C Threads can be built with.



Using Shared Variables

All global and static variables are shared among all threads:  If one 
thread modifies such a variable, all other threads will observe the new 
value.  In addition, a variable reachable from a pointer is shared 
among all threads that can dereference that pointer.  This includes 
objects pointed to by shared variables of pointer type, as well as 
arguments passed by reference in cthread_fork().

When pointers are shared, some care is required to avoid problems 
with dangling references.  You must ensure that the lifetime of the 
object pointed to is long enough to allow the other threads to 
dereference the pointer.  Since there's no bound on the relative 
execution speed of threads, the simplest solution is to share pointers to 
global or heap-allocated objects only.  If a pointer to a local variable is 
shared, the function that variable is defined in must remain active until 
it can be guaranteed that the pointer will no longer be dereferenced by 
other threads.  The synchronization functions can be used to ensure 
this.

Unless a library has been designed to work in the presence of 
reentrancy, the operations provided by the library must be presumed 
to make unprotected use of shared data.  Hence, you must protect 
against this through the use of a mutex that's locked before every 
library call (or sequence of library calls) and unlocked afterwards.



Synchronization of Variables

This section describes mutual exclusion and synchronization 
functions, which are used to constrain the possible interleavings of 
threads' execution streams.  These functions manipulate mutex and 
condition variables, which are defined as follows:

typedef struct mutex {...} *mutex_t;

typedef struct condition {...} *condition_t;

Mutually exclusive access to mutable data is necessary to prevent 
corruption of data.  As a simple example, consider concurrent 
attempts to update a simple counter.  If two threads fetch the current 
value into a (thread-local) register, increment, and write the value 
back in some order, the counter will only be incremented once, losing 
one thread's operation.  A mutex solves this problem by making the 
fetch-increment-deposit action atomic.  Before fetching a counter, a 
thread locks the associated mutex, and after depositing a new value 
the thread unlocks the mutex:

mutex_lock(m); 
count -= 1; 
mutex_unlock(m);

If any other thread tries to use the counter in the meantime, it will 
block when it tries to lock the mutex.  If more than one thread tries to 
lock the mutex at the same time, the C Threads package guarantees 
that only one will succeed; the rest will block.

Condition variables are used when one thread wants to wait until 
another thread has finished doing something.  Every condition 
variable should be protected by a mutex.  Conceptually, the condition 
is a boolean function of the shared data that the mutex protects.  
Commonly, a thread locks the mutex and inspects the shared data.  If 
it doesn't like what it finds, it waits using a condition variable:

mutex_lock(mutex_t m); 
...  
while ( /* condition isn't true */ ) 
	condition_wait(condition_t c, mutex_t 
m); 
...  
mutex_unlock(mutex_t m);

This operation also temporarily unlocks the mutex, to give other 
threads a chance to get in and modify the shared data.  Eventually, one 
of them should signal the condition (which wakes up the blocked 
thread) before it unlocks the mutex:

mutex_lock(mutex_t m); 
...			/* modify shared data */ 
condition_signal(condition_t c); 
mutex_unlock(mutex_t m);

At that point, the original thread will regain its lock and can look at 
the shared data to see if things have improved.  It can't assume that it 
will like what it sees, because some other thread may have slipped in 
and altered the data after the condition was signaled.

You must take special care with data structures that are dynamically 
allocated and deallocated.  In particular, if the mutex that's controlling 
access to a dynamically allocated record is part of the record, make 
sure that no thread is waiting for the mutex before freeing the record.

Attempting to lock a mutex that one already holds is another common 
error.  The offending thread will block waiting for itself.  This can 
happen when a thread is traversing a complicated data structure, 
locking as it goes, and reaches the same data by different paths.  
Another instance of this is when a thread is locking elements in an 
array, say to swap them, and it doesn't check for the special case that 
the elements are the same.

You must be careful to avoid deadlock, a condition in which one or 
more threads are permanently blocked waiting for each other.  The 
above scenarios are a special case of deadlock.  The easiest way to 
avoid deadlock with mutexes is to impose a total ordering on the 
mutexes, and then ensure that threads only lock mutexes in increasing 
order.

You must decide what kind of granularity to use in protecting shared 
data with mutexes.  The two extremes are to have one mutex 
protecting all shared memory, and to have one mutex for every byte of 
shared memory.  Finer granularity normally increases the possible 
parallelism, because less data is locked at any one time.  However, it 
also increases the overhead lost to locking and unlocking mutexes and 
increases the possibility of deadlock.



Program Examples:  C Threads

This section demonstrates the use of the C Threads library functions in 
writing a multithreaded program.  The program is an example of how 
to structure a program with a single master thread that spawns a 
number of concurrent slaves.  The master thread waits until all the 
slaves have finished and then exits.

A random number generator determines the ªlifeº of the slave 
processes.  Once created, the slave processes simply loop calling a 
function that makes the processor available to other threads.  The 
random number generator determines the length of this loop.  In a 
more useful version of this program, each slave process would do 
something while looping.

#include &lt;stdio.h&gt; 
#include &lt;cthreads.h&gt;

int 		count;		/* number of slave threads 
active */ 
mutex_t		lock;		/* mutual exclusion for 
count */ 
condition_t	done;		/* signaled each time a 
slave thread 						   finishes */

extern long random(void);

void init() 
{ 
	count = 0;			/* counter:  current 
number of slaves */ 
	lock = mutex_alloc();		/* allocate a 
mutex, assign it 								   to the 
variable lock */ 
	done = condition_alloc();	/* allocate a 
condition object, 								   assign 
it to done€*/ 
	srandom(time((int *) 0));	/* initialize 
random number 
								   generator */ 
}

/*
 * Each slave just counts up to its argument, 
yielding the 
 * processor on each iteration.  When it's 
finished, it 
 * decrements the global count and signals that 
it's done.
 */ 
void slave(int n) 
{ 
	int i;

	for (i = 0; i &lt; n; i += 1) 
		cthread_yield(); 
	/*
	 * If any thread wants to access the 
count variable, it 	 * first locks the mutex.  When 
the mutex is locked, any 	 * other thread wanting 
the count variable must wait until 	 * the mutex 
is unlocked.  
	 */ 
	mutex_lock(lock); 
	count -= 1; 
	printf("Slave finished %d cycles.\n", 
n); 
	condition_signal(done);	/* signaled each 
time a slave 								   finishes */ 
	mutex_unlock(lock); 
}

/*
 * The master spawns a given number of slaves and 
then waits 
 * for them all to finish.  
 */ 
void master(int nslaves) 
{ 
	int i;

	for (i = 1; i &lt;= nslaves; i += 1) { 
		mutex_lock(lock); 
		/* Increment count with the creation 
of each slave 			   thread.  */ 
		count += 1; 
		/* Fork a slave and detach it, since 
the master never 		   joins it individually.  
*/ 
		cthread_detach(cthread_fork(slave, 
random() % 1000)); 
		mutex_unlock(lock); 
	} 
	mutex_lock(lock); 
	/*
	 * Master thread loops waiting on the 
condition done.  Each 	 * time the master thread is 
signaled by a condition_signal 	 * call, it tests 
the count for a value of zero.  
	 */ 
	while (count != 0) 
		condition_wait(done, lock); 
	mutex_unlock(lock); 
	printf("All %d slaves have finished.\n", 
nslaves); 
	cthread_exit(0); 
}

main() 
{ 
	init(); 
	master((int) random() % 16);	/* create 
master thread and 									   up 
to 15 slaves */ 
}

</PRE>

