COMP421
Unix Environment for Programmers
Lecture 15: Threads___________________________________________

Jeff Wiegley, Ph.D.
Computer Science
jeffw@csun.edu

10/17/2005

     

‘‘Chains do not hold a marriage together. It is threads, hundreds of tiny threads, which sew people together through the years..’’

–Robert Oxton Bolt

1


Multiprocessing______________________________________

There are many problems that are solved best by parallel execution of tasks.

Historically, Unix handled these through its standard process model of forked behavior.

Eash task handled by an independent (yet related) process.

2


fork() disadvantages._______________________________

Although the fork() solution works, there are a number of disadvantages associated with it:

But time and problems and march on.

The need for multiprocessing and asyncronous computation have increased to the point where Unix must evolve to maintain efficiency.

3


Threads:_______________________________________________

The instructor believes that Unix owes a significant portion of its success and longevity to its excellent original design.

Without recreating a new operating system from scratch Unix addressed the need for “light-weight” processes by introducing the concept of threads.

Threads are like a new process but the creation of thread doesn’t create a whole new process.

Instead the thread is created within the original process.

This requires some change in the kernel’s process table, scheduling and system calls but aside from this the rest of Unix remains relatively unchanged.1

4


Thread advantages:__________________________________

Threads provide the following advantages over forked processes:

5


Thread creation:_____________________________________

Threaded programs start as any other process. The process just happens to consist of a single thread.

Creating more threads is really easy:

      int err;  
      pthread_t ntid;  
      err = pthread_create(&ntid, NULL, thread_function, NULL);

6


Simple Examples:____________________________________

#include <stdio.h>  
#include <sys/types.h>  
#include <unistd.h>  
#include <pthread.h>  
 
pthread_t ntid;  
 
void printids(const char *s)  
{  
  pid_t     pid;  
  pthread_t tid;  
 
  pid = getpid();  
  tid = pthread_self();  
  printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid,  
         (unsigned int)tid, (unsigned int)tid);  
}  
 
void *thr_fn(void *arg)  
{  
  printids("new thread: ");  
  return((void *)0);  
}  
 
int main(int argc, char *argv[])  
{  
  int err;  
  err = pthread_create(&ntid, NULL, thr_fn, NULL);  
  err = pthread_create(&ntid, NULL, thr_fn, NULL);  
  printids("main thread: ");  
  sleep(1);  
  exit(0);  
}

output:

$ gcc threadtest.c -lpthread  
$ ./a.out  
main thread:  pid 29739 tid 3084707520 (0xb7dce6c0)  
new thread:  pid 29739 tid 3084704688 (0xb7dcdbb0)  
new thread:  pid 29739 tid 3076311984 (0xb75ccbb0)  
$

Notice that the pid of each thread is the same. The threads are distinguished within the process by their thread id instead.4

7


Concurrency issues still arise:______________________

Although thread synchronization is simpler in threads concurrency issues still arise. The previous sample took care of two problems:

  1. When the main thread exits, all threads exit. So a call to sleep(1) has been inserted into the main thread to give the other threads time to wake up and complete their task.5
  2. Although the thread id is returned in ntid and ntid is global it would seem possible for the child thread to obtain its thread id by reading from ntid directly. However, if the thread begins executing before the main thread has had time to assign the result into ntid then the child would read an initialized value. So a call to pthread_self() is used instead.

8


Thread termination:_________________________________

Any call to exit(),  Exit() or  exit() terminates the entire program (and all threads).

A thread can terminate itself without terminating the program in any of three ways:

  1. Simply return from the start function that was passed to pthread create.
  2. A thread can be cancelled by another thread in the same process.
  3. A thread can call pthread exit(coid *rval ptr).

pthread join(pthread t thread, void **rval prt) is a handy function.

The calling thread blocks until the thread with thread id thread exits.

rval ptr returns the reason for the threads exit.

Instead of an arbitrary length call to sleep() it is better for the main thread to join with every thread that was created before exiting.

9


rval ptr warning:_____________________________________

rval ptr is just a mechanism to pass any memory pointer from the exiting thread to the joining thread.

WARNING: Using a structure allocated on the exiting thread’s stack is dangerous. The stack can be deallocated before the joining thread is done with the data passed to it.

You can solve this problem in a number of ways:

10


Cancelling:____________________________________________

Any thread can request that another thread be cancelled by calling:

      void pthread_cancel(pthread_t tid);

Any thread can elect to ignore or otherwise control how it is cancelled.

pthread cancel() does not block until the thread cancels. instead it is merely a request.

11


Cleaning Up:_________________________________________

Threads can arrange to have a list of functions that are called when it exits.

This is useful for performing clean-up tasks prior to execution.

Two functions are provided for manipulating the stack of functions to be called.

      void pthread_cleanup_push(void (*rtn)(void *), void *arg);  
      void pthread_cleanup_pop(int execute);

push pushes the function rtn onto the stack of functions to be called.

pop takes the last pushed function off the stack. If execute is non-zero then the function is executed in conjunction with the pop.8

The functions are executed in the reverse order that they were pushed on. (As one would expect from a stack “LIFO”).

12