CSCI 4630
Spring 2001
Programming assignment 2

Due:3/20/01

The assignment, part 1

Write a program that creates two POSIX threads. Each thread should repeatedly perform a modification of a shared variable or variables. You can select the operation to use, but make it one that is unsafe when done outside a critical section.

After the threads have done enough operations, you should expect your shared data to become corrupted. Design your threads to detect the corruption, report it and stop. Only report a situation where the data has become inconsistent because the update was not done in a critical section.

You will want each thread to tell the other thread that an error in the data has been observed, by storing a value in a shared variable. That way, the other thread can stop when the error has been detected.

Run your program. Time it to see how long it takes before the data becomes corrupted. Add a comment to the top of the program text indicating about how long it took to detect corrupted data.


The assignment, part 2

Modify your program. Create a semaphore before creating the threads. Use the semaphore to protect the update of the shared variable or variables. Rerun the program, showing that the shared data does not become corrupted.

POSIX threads

This describes only the most basic aspects of how to create and manage POSIX threads. It is assumed that you are writing your program in C or C++, and using a POSIX compliant system. (Solaris 2.x and Windows NT 4.0 both have POSIX interfaces.) I have tested this under Solaris 2.x.

To create threads, you start by creating a structure that describes desired thread attributes. You can then use that structure to create many threads with those attributes. Create a variable attr of type pthread_attr_t, and initialize it as follows.

      pthread_attr_init(&attr);
      pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
The first line installs the default attributes. The second line indicates that the thread manager should switch between threads in a way similar to the way the system's process manager switches between processes. That is, preemptive scheduling of the threads is used. (The default is to use cooperative multiprogramming among the threads.)

Then create the thread as follows.

      status = pthread_create(&tid, &attr, func, NULL);
where
  1. tid is a variable of type pthread_t (typically another name for unsigned int). It will be set to the thread id, an integer.

  2. func is a function that the thread will run. The prototype for func should be

          void* func(void*);
    
    (You don't have to call the function func. You can call it any name that you like. But it must take a parameter of type void*, and produce a result of type void*.) Make func return NULL.

  3. status is a variable of type int. Function pthread_create returns 0 if the thread was created, and a nonzero value if there was an error that prevented creation of the thread.

  4. The last parameter of pthread_create is the parameter that is passed to func when the thread starts. It has type void*. So, when pthread_create(tid,func,a,p) starts, it runs f(p). The line above shows this parameter set to NULL. If you like, you can set it to an integer k, as in

          status = pthread_create(&tid, &attr, func, (void*) k);
    
    That allows you to used the same function func with two different parameters, so that the threads can tell which is which.

When the thread is created it starts running immediately.

You will need to create two threads, and then wait for the threads to terminate. (If you don't wait, and you let the main program terminate, the threads that it created will be destroyed prematurely.) Function pthread_join allows one thread to wait for another one. Use

      pthread_join(tid,NULL);
to wait for thread number tid to terminate before continuing. Since you will have created two threads, you will have to wait for both of them. You will have to wait for them in a specific order. It does not matter which one finishes first. A terminated thread waits until another thread joins with it before it disappears. (It becomes a zombie thread.)

Compiling and Linking

You will need to include header file pthread.h to use POSIX threads. Also, when you link your program, you will need to use the pthread library. If you compile using gcc, you write
   gcc myprog.c -lpthread
It is very important that you use the pthread library. If you forget to use it, a default pthread_create function will be provided for you that does nothing at all. You will wonder why your program is not doing anything.

When using threads, it is a good idea to include directive

#define _REENTRANT
to force the compiler to generate reentrant code. (Most compilers do this anyway.) Reentrant code has a read-only TEXT segment (where the program is stored) so that it can be run simultaneously by more than one thread.

Timing your program

You can get your program to print the current time of day as follows.
      time_t tloc;
      char buff[50];
      time(&tloc);
      printf("%s", ctime_r(&tloc, buff, 50));
Include header file time.h to use these. The POSIX function ctime_r is a little different. It takes only the first two parameters; drop the 50 for it.

Semaphores

You can create a POSIX semaphore that can synchronize threads or processes. They are stored in the file system, and are somewhat complicated. Here, I describe Solaris semaphores because they are simpler. See the man page for sem_open for creation of POSIX semaphores. You can use either kind. Include synch.h to use Solaris semaphores. You can create a semaphore that can synchronize threads that belong to the same process as follows.
     sema_t sp;
     sema_init(&sp, 1, USYNC_THREAD, NULL);
Now use
     sema_wait(&sp);
to do a wait on semaphore sp, and
     sema_post(&sp);
to do a signal on sp. When you are finished with the semaphore, destroy it using
     sema_destroy(&sp);

What to turn in

Turn in both versions of the program, one without semaphores and one with semaphores. Use the handin program, with assignment number 2. So if your two programs are called thread1.c and thread2.c, use the following to hand in the assignment. (Run on one of the 320 lab machines.)
  alias handin "/export/stu/classes/csci4630/bin/handin csci4630"
  handin 2 thread1.c thread2.c