[Return Value of Thread Functions], [Pthreads], [Threadsafe Functions], [Two Examples], [Locks for Pthreads]
Good source of information on threads are the books:
Before I forget, on our systems remember to compile programs that use threads as follows
Beware that cc likes "pthread_attr_default" while gcc likes "NULL".
POSIX has a large number of functions for managing threads, using mutexes, using condition variables, etc. Check these services with the command
Threads are not easy to understand well. There are all sorts of complexities hidden by names such as user threads, kernel threads, light-weight threads as it is discussed in the textbook. There are obvious questions we will not explore. For example:
if ((pid=fork()) < 0) { perror("cannot fork"); exit(1); }where perror used the errno information, with thread functions we have to do things like
if ((rc = pthread_create(..,..,..,..)) != 0) { fprintf(stderr, "Cannot create thread %s\n", strerror(rc)); exit(1); }By the way, for traditional system calls that set the int errno, originally this errno was global to all threads, but in recent Unix implementations of threads, errno has become local to each thread.
Since threads are executed concurrently in the same address space
and control can be transferred between them at any time, we have to
be very cautions in using them
and make sure that concurrent executions of functions
do not result in problems. We say that a function is threadsafe
when it can be executed without problems by concurrent threads.
Usually this means that the function uses only local variables and
read-only global variables. In the case of functions that that write
to global storage, they may be made threadsafe if they use
appropriate locks.
If we protect a thread unsafe function with a lock (i.e. we precede the
call with a lock and follow it with an unlock) we may or not become
threadsafe.
Suppose I write a function
/* keep a running total of the values passed in calls*/ int adder(int n) { static int sum = 0; sum += n; return sum; }This function, when used by concurrent threads may fail (it is not threadsafe) because "sum += n;" is a critical section. But even if we use locks the function remains unthreadsafe, in the following sense: In a thread Alice uses "adder" to keep track of her deposits, while in another thread Bob would like to keep track of his deposits. Clearly that will not happen: adder is unsafe. In general, whenever we have stateful functions (i.e. functions that maintain a state acroos calls) we are dealing with potentially unsafe functions. In this case we need to pass the state as a parameter. In our case adder could be rewritten:
int adder(int *sum, int n) { *sum = *sum +n; return *n;}Many functions in the standard C library are not threadsafe, though threadsafe versions may also be available as we have seen for rand and rand_r.
Another thing to be aware of when using threads is that it is dangerous to use pointers from a thread to locations on the stack of another thread. For example if thread A calls a function moo and in there declares a variable x and passes x as parameter to a thread B. Then moo returns. Now B is accessing a location that has been deallocated by A, and perhaps reallocated with a different meaning. Moral: to a thread pass only dynamically allocated data, or static data.
As an example, when using threads we call rand_r instead of rand to generate random numbers because rand_r is re-entrant (i.e. threadsafe).
Here are the random functions we use in non-threaded programs:
void srand(unsigned int seed); int rand(void);srand sets some global variable to seed and each call to rand updates and returns the value of the global variable. If srand and rand are called concurrently by more than one thread the global location holding the seed is clobbered and the threads get unpredictable [unrepeatable] sequences of integers. When using threads we call:
int rand_r(unsigned int *seedptr);and pass the address of a seed local to the calling thread [i.e. different threads use different seed variables and initialize them directly with an assignment, not srand].
#include <pthread.h> int pthread_create( pthread_t *thread, // The thread that is created const pthread_attr_t *attr,//attributes for thread; usually // we use pthread_attr_default (or NULL) void * (*start_routine)(void *), //function executed by thread void * arg); // address of argument passed to startroutine // Returns 0 iff successful The created thread is ready as soon as created and inherits scheduling discipline and signal mask from its creator. The definition of pthread_attr_t on my alpha is: typedef struct __pthread_attr_t { long _Pfield(valid); __pthreadLongString_t _Pfield(name); __pthreadLongUint_t _Pfield(arg); __pthreadLongUint_t _Pfield(reserved)[19]; } pthread_attr_t; Not exactly easy to use. It is best to go with the defaults or to use functions to set particular aspects of the attribute. Check the meaning of such functions with the Unix shell command: apropos pthread_attr #include <pthread.h> int pthread_delay_np( struct timespec *interval); /* delay thread for specified time */ where struct timespec { time_t tv_sec; /* seconds */ long tv_nsec;} /* nanoseconds */
Our first example is a threaded version of the Hello World example. The principal thread (i.e. the only thread that exists when we start a process) creates a new thread then waits some time before terminating. The created thread prints in a loop "Hello World!".
The program will print out 16 times the string "Hello World!" and then terminate. Notice that when the main thread of program terminates so do all other threads.
In the second example we run three
concurrent threads [this number can be easily changed].
Each thread executes code that writes
to different locations so as not to have race conditions.
The exception is the variable
If you run this program you will notice:
A thread can terminate its own execution with the command:
#include <pthread.h> void pthread_exit(void *status); It exits the current thread and returns status to the thread waiting in pthread_join, if any. This command does not by default return the thread's resources to the system. If the main function of a threaded program uses "return" to terminate, all running threads are ended. If insteand the main function uses "pthread_exit", then the program continues executing until all its threads have terminated.
We can wait for termination of a specific thread with the function pthread_join:
#include <pthread.h> int pthread_join(pthread_t who, void **status); It suspends the calling thread until the thread who has terminated. Status will receive the value returned by the terminating thread when it exited with the pthread_exit command. pthread_join returns 0 iff successful. When a terminated thread is joined, its resources, including memory, are reclaimed by the system.
We can modify the previous program so that the main thread waits for the termination of all the created threads by replacing in main the lines
/* Wait a while then exit: all existing threads will die */ maintime.tv_sec = TOTALRUN; maintime.tv_nsec =0; pthread_delay_np(&maintime);with the lines
/* Wait for all other threads to terminate */ for (i=0; i < THREADSCOUNT; i++) { pthread_join(states[i].t, NULL); printf("Thread %d has terminated\n", i);
You can mark for deletion and reclaim the storage and other resources associated with a thread (of course, after it has terminated executing) with the command:
#include <pthread.h> int pthread_detach(pthread_t thread);
This command will not terminate a thread that is executing, only indicating
that we want to reclaim automatically its storage when it terminates execution.
Other ways of reclaiming the resources of a thread are:
We can use mutual exclusion semaphores, or locks, or mutexes with pthreads. These locks should be global to the threads.
#include <pthread.h> int pthread_mutex_init( pthread_mutex_t *mutex; /* The mutex being created */ pthread_mutexattr_t *attr); /* usually the default, NULL, i.e. pthread_mutexattr_default */ int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_mutex_destroy(pthread_mutex_t *mutex); /* When done with a mutex we can free its resources this way */
There are three kinds of mutexes depending on the value of the
pthread_mutexattr_t attribute. We could have MUTEX_FAST_NP (the default),
to be used
in the standard lock..unlock protocol; MUTEX_RECURSIVE_NP: which allows
one thread to do things like "lock .. lock .. unlock .. unlock";
MUTEX_NONRECURSIVE_NP is like the fast lock, but with better debugging
facilities. One normally uses for the attribute the default value NULL,
i.e. pthread_mutexattr_default.
Mutexes are intended to be used in the pattern "lock .. unlock"
where the locking and unlocking operations are made by the same
thread. This pattern is enforced in all mutexes except the
fast (or normal) mutexes. In this case no check is made to
enforce the requirement that unlock is done by the same thread that
locked the mutes. Thus we can use fast mutexes as if they were
"blocking semaphores" to enforce priority constraints between activities.
For example if I want thread 1 to do A before thread 2 can start B,
we will create, initialize and lock a global fast mutex m. Then
thread 2 before executing B will try to lock m, and thread 1 after
finishing A will unlock m. [This is much easier than what would be needed
if the ownership constraint is enforced.]
Here is a program with threads that use locks to share a resource.
ingargio@joda.cis.temple.edu