CSCI.4210 Operating Systems
Posix thread system calls

The Posix standard defines a number of thread system calls. The posix function to create a new thread within the same process has the following rather ugly function prototype


#include <pthread.h>

int pthread_create(pthread_t *thread,  const  pthread_attr_t
*attr, void *(*start_routine, void*),void *arg);

This system call has four arguments, the first *thread is a pointer to the thread id number (the type pthread_t is an int). The calling program may need this value later for thread synchronization. The data type pthread_attr_t allows the calling program to set some of the attibutes of the thread, such as the size of the stack. If this is set to NULL, the thread has default attributes which are appropriate for most purposes.

The third argument start_routine is the name of a function where the newly created thread will start. This function must have the following prototype
void *start_routine(void *arg)
(replacing start_routine with the name of the function.) It must take one argument, a pointer. The last argument is the pointer to the argument.

As is usually the case, the pthread_create call will return a zero if it successfully createed the new thread and a negative value if it failed, and if it failed, the external global variable errno will be set to a value which indicates why it failed.

The threads will be running concurrently; which means that they are all running seemingly at the same time, and there is no assurance about which thread will run first. This can create a race condition; in a race condition, two or more events happen at about the same time, and there is no assurance that one event will happen before the other; the order of the events is indeterminate.

One possibility with threads is that the main calling thread can terminate before all of the threads have terminated. Since all threads are running in the same process space, when the main thread terminates, the entire process dies. In fact, if any thread calls exit(), the entire process immediately terminates. This means that it is possible that some of the threads to die before they have completed their work.

There are two other posix thread system calls that help to address this problem. To terminate a single thread without killing the entire process, use the system call
void pthread_exit(void *value_ptr);
The argument is a pointer to a return value, but it can be set to NULL.

The calling thread can wait for a particular thread to terminate with the call
int pthread_join(pthread_t thread, void **value_ptr); The first argument is the thread id that the process is waiting for, and the second is a pointer to the argument passed back by the thread. This can be set to NULL. This function will block until the thread terminates. Note the similarity in function between this function and the wait function, which waits for a child to die.

Here is some very simple thread code

/* Alert: link to the pthreads library by appending
   -lpthread to the compile statement */
#include <pthread.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h> /* for sleep */
#include <stdlib.h> /* for malloc */

#define NUM_THREADS 5

void *sleeping(void *);   /* thread routine */
pthread_t tid[NUM_THREADS];      /* array of thread IDs */

int main()
{
  int *sleeptime, i, retval;
  for ( i = 0; i < NUM_THREADS; i++) {
     sleeptime = (int *)malloc(sizeof(int));
     *sleeptime = (i+1)*2;
     retval =pthread_create(&tid[i], NULL, sleeping, 
             (void *)sleeptime);
     if (retval != 0) {
         perror("Error, could not create thread");
     }
  }
  for ( i = 0; i < NUM_THREADS; i++)
      pthread_join(tid[i], NULL);
  printf("main() reporting that all %d threads have terminated\n", i);
  return (0);
}  /* main */

void *sleeping(void *arg)
{
    int sleep_time = *(int *)arg;
    printf("thread %d sleeping %d seconds ...\n", 
           pthread_self(), sleep_time);
    sleep(sleep_time);
    printf("\nthread %d awakening\n", pthread_self());
    return (NULL);
}

Passing arguments to the thread routine requires special attention. The argument is a pointer. It is usually important that the argument to each thread point to different memory, which is why the loop that calls the thread allocates new memory with malloc each time. Also, make sure that the calling function does not change the value of the memory that arg is pointing to after it creates the thread. Because there is a race condition between the calling function and the newly created thread, we do not know whether the thread or the calling function will run first

If you wrote the loop in the obvious (but wrong) way, like this:

  for (i=0;i<5;i++) {
    retval = pthread_create(&threadIds[i], NULL,
             ThreadRoutine, &i);
    ...
the program would run without errors, but the argument to all five threads would probably be the same, rather than different for each because you are passing the same address each time.

Returning a value from a dying thread using pthread_exit() will test your pointer skills. The argument to this function is a void pointer. A void pointer can point to any data type. The second argument to the function pthread_join is a pointer to a pointer to void, which can also point to any particular data type (presumably the same type as the argument to pthread_exit(). When the call to pthread_join returns, the second argument will point to the argument returned by pthread_exit for that thread.

Here is a short program which demonstrates this. It makes copies of files. The names of the files to be copied are passed in as arguments to main (up to a max of 10). A separate thread is created for each file. The name of the copied file is formed by prepending the string Copy_Of_ to the filename (for example, if the filename was myfile, the copy would be called Copy_Of_myfile). The thread returns the number of bytes copied, and the main thread then displays the total number of bytes copied in all of the files.

/* A program that copies files in parallel
   using pthreads */
/* Alert: link to the pthreads library by appending
   -lpthread to the compile statement */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

/* pthread_t copy_tid;*/

extern int errno;
#define BUFSIZE   256

void *copy_file(void *arg)
{
    int infile, outfile;
    int bytes_read = 0;
    int bytes_written = 0;
    char buffer[BUFSIZE];
    char outfilename[128];
    int *ret;

    ret = (int *)malloc(sizeof(int));
    *ret = 0;
    infile = open(arg,O_RDONLY);
    if (infile < 0)  {
        fprintf(stderr,"Error opening file %s: ", 
             (char *)arg);
        fprintf(stderr,"%s\n",strerror(errno));
        pthread_exit(ret);
    }
    strcpy(outfilename,"Copy_Of_");
    strcat(outfilename,arg); 
    outfile = open(outfilename,O_WRONLY | O_CREAT | O_TRUNC, 
             S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    if (outfile < 0) {
        fprintf(stderr,"Error opening file %s",outfilename);
        fprintf(stderr,"%s\n",strerror(errno));
        pthread_exit(ret);
    }
    while (1) {
         bytes_read = read(infile, buffer, BUFSIZE);
         if (bytes_read == 0) 
            break;
	  else if (bytes_read < 0) {
		  perror("reading");
                  pthread_exit(ret);
	  }
	  bytes_written = write(outfile, buffer, bytes_read);
          if (bytes_written != bytes_read) {
	         perror("writing");
                 pthread_exit(ret);
	  }
          *ret += bytes_written;
    }
    close(infile);
    close(outfile);
    pthread_exit(ret);
    return ret; /* we never get here, but the compiler
                      likes to see this line */
}

int main(int argc, char *argv[])
{
    pthread_t tid[10]; /* max of 10 possible threads */

    int total_bytes_copied = 0;
    int *bytes_copied_p;
    int i;
    int ret;

    for (i=1;i < argc;i++) {
      ret = pthread_create(&tid[i], NULL, copy_file, 
               (void *)argv[i]);
      if (ret != 0) {
           fprintf(stderr,"Could not create thread %d: %s\n",
            i, strerror(errno)); 
           fprintf(stderr,"ret is %d\n",ret);
      }
    }
    /* wait for copies to complete */

    for (i=1;i < argc;i++) {
      ret = pthread_join(tid[i],(void **)&(bytes_copied_p));
      if (ret != 0) {
         fprintf(stderr,"No thread %d to join: %s\n",
                 i, strerror(errno));
      }
      else {
         printf("Thread %d copied %d bytes from %s\n",
               i, *bytes_copied_p, argv[i]);
         total_bytes_copied += *bytes_copied_p;
      }
    }
    printf("Total bytes copied = %d\n",total_bytes_copied);
    return 0;
}
Pay particular attention to the way that pthread_exit passes a value, in this case an integer, back to the main thread; and note how the main thread uses the second argument of pthread_join to access this value. You may find this syntax (void **)&(bytes_copied_p) a little unsettling. The variable bytes_copied_p is a pointer to an integer. By preceding it with an amersand (&) we are passing its address to the function. This will permit the function to change what it is pointing to, which is a good thing because when you call the function, it is not pointing anywhere. The function changes its value so that it is pointing to the argument of thread_exit. The (void **) casts this value to a pointer to a pointer, which keeps the compiler happy.

The typical design of a program that uses threads is to have a main thread that creates some number of child threads and then waits for them to terminate. Both of the examples above used this design. However, any thread can create new threads, and it is not necessary for the parent thread to wait for the children to terminate.

Here is an exercise on threads

Win32 APIs for Threads

The Win32 API to create a new thread is

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security attributes
  DWORD dwStackSize,                         // initial thread stack size
  LPTHREAD_START_ROUTINE lpStartAddress,     // pointer to thread function
  LPVOID lpParameter,                        // argument for new thread
  DWORD dwCreationFlags,                     // creation flags
  LPDWORD lpThreadId                         // pointer to receive thread ID
);

Although this takes a few more arguments than pthread_create it works in a very similar way. If the first argument lpThreadAttributes is set to NULL, and the second argument dwStackSize is set to zero, appropriate default values will be assigned. The third argument lpStartAddress should be set to the name of the function to be called. The fourth argument lpParameter is a pointer to the argument to be passed to the function. The fifth argument dwCreationFlags should be set to zero. The last argument is a pointer to a DWORD. This will be set by the function.

The function returns a HANDLE value if successul. If it fails, the return value will be NULL.

The called function must have the following signature:
DWORD WINAPI ThreadProc(LPVOID lpParameter); replacing ThreadProc with the name of the function.

The Win32 equivalent of pthread_join is

DWORD WaitForSingleObject(
  HANDLE hHandle,        // handle to object to wait for
  DWORD dwMilliseconds   // time-out interval in milliseconds
);
This is a very important function because it is used to wait for all kinds of different events, and we will see it a lot in this course. In this case it is used to wait for a thread to terminate. The first argument hHandle is the handle returned from CreateThread. Like pthread_join, this function blocks until the thread terminates. However, unlike pthread_join this function allows you to specify how long you are willing to wait for the event before becoming unblocked. The second argument is the number of milliseconds to wait. if this value is zero, the function returns immediately, even if the thread has not terminated. Another possible value is the keyword INFINITE, which causes the function to block indefinitely if the thread does not terminate.

Here is why this may be useful. Supposed there is an infinite loop or other code in the thread function which means that the thread will never terminate. If the second argument of WaitForSingleObject was set to INFINITE, this would mean that the main thread would be indefinitely blocked. On the other hand, if the second argument was set to, say, 1000, then it would wake up after a one second interval.

Here is a simple sample program which corresponds to the first sample program using pthreads.

#include <windows.h>
#include <stdio.h>

DWORD WINAPI ThreadRoutine(LPVOID lpArg)
{
    int a;
	a = *(int *)lpArg;
	fprintf(stderr,"My argument is %d\n",a);
	return NULL;
}

int main()
{
	int i;
	int *lpArgPtr;
	HANDLE hHandles[5];
	DWORD ThreadId;
	
	for (i=0;i < 5;i++) {
		lpArgPtr = (int *)malloc(sizeof(int));
		*lpArgPtr = i;
		hHandles[i] = CreateThread(NULL,0,ThreadRoutine,lpArgPtr,0,&ThreadId);
		if (hHandles[i] == NULL) {
			fprintf(stderr,"Could not create Thread\n");
			exit(0);
		}
		else printf("Thread %d was created\n",ThreadId);
	}

	for (i=0;i < 5;i++) {
		WaitForSingleObject(hHandles[i],INFINITE);
	}
	return 0;
}

To terminate a particular thread without terminating the entire process, use the API
VOID ExitThread(DWORD ExitCode);
where ExitCode is a value to be returned to another thread.

A thread can read the ExitCode of another thread which has terminated with the API
BOOL GetExitCodeThread(HANDLE hThread, LPDWORD lpdwExitCode);
This should only be called if the thread has terminated, so it is usually used in combination with the API
WaitForSingleObject

Here are links to the on line help pages for these functions
CreateThread
WaitForSingleObject
ExitThread
GetExitCodeThread

Thread Safe Libraries

The C library was written for single threaded functions, and many library functions use global variables to store intermediate results. This means that they are not thread safe. If two or more threads are accessing the same library function at the same time, the function can produce an erroneous answer or a memory exception error.

The documentation for most library functions should specify whether or not the system call is thread safe.

Posix Thread Synchronization

The Posix thread mechanism (pthreads) provides a simple mutex facility as defined in the previous module. The pthread library defines an opaque data type pthread_mutex_t. An instance of a pthread_mutex_t should be defined globally and initialized when it is declared with the macro PTHREAD_MUTEX_INITIALIZER. There are three system calls which perform operations on a mutex, each of which takes a pointer to a pthread_mutex_t as an argument.

int pthread_mutex_lock(pthread_mutex_t *mutex);
This function checks to see if the mutex is locked. If it is not, it locks the mutex and returns, thus allowing the calling thread to continue. If the mutex is locked, the thread blocks until the mutex becomes unlocked. At this time the mutex is set to the locked state again but the function returns, allowing the thread to continue. This should be called before a thread enters its critical section.
int pthread_mutex_trylock(pthread_mutex_t *mutex);
This is similar to the above function but it always returns immediately. The return value is zero if the mutex had previously been unlocked and the thread has successfully locked the mutex. If the mutex is locked, the function returns a non-zero value.
int pthread_mutex_unlock(pthread_mutex_t *mutex);
This unlocks a locked mutex. This should be called by a thread after it leaves its critical section.
Here is some skeleton sample code which demonstrates how to use a mutex in pthreads. Recall that the aim is to make sure that only one thread at a time is in its critical section.
/* pthreadmutex.c - a demo of pthread mutual exclusion */
#include <pthread.h>
#include <stdlib.h> /* for random() */
#include <stdio.h> /* for printf() */
#include <unistd.h> /* for sleep() */

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; /* declare the mutex globally */

void CriticalSection(int num)
{
  printf("Thread %d is in its critical section\n", num);
  sleep(random() % 4);  /* sleep for zero to four seconds */
  printf("Thread %d is leaving its critical section\n", num);
}

void NonCriticalSection(int num)
{
  printf("Thread %d is in its noncritical section\n", num);
  sleep(random() % 4);   /* sleep for zero to four seconds */
  printf("Thread %d is leaving its noncritical section\n", num);
}

void* ThreadController(void *arg)
{
  int *num = (int *)malloc(sizeof(int));
  int i;
  *num = *(int *)arg;
  for (i=0;i < 4;i++) {
    NonCriticalSection(*num);
    pthread_mutex_lock(&mutex);
    CriticalSection(*num);
    pthread_mutex_unlock(&mutex);
  }
  pthread_exit(num);
  return num; /* we never get here */
}

int main()
{
  int i;
  int retval;
  int *arg;
  pthread_t threadid[5];

  for (i=0;i < 5;i++) {  /* create five threads */
    arg = (int *)malloc(sizeof(int));
    *arg = i+1;
    retval = pthread_create(&threadid[i],NULL, ThreadController,arg);
    if (retval != 0) {
      fprintf(stderr,"ERROR creating thread");
    }
    
  } 
  for (i=0;i<5;i++) {
    arg = (int *) malloc(sizeof(int));
    retval = pthread_join(threadid[i],(void **)&arg);
    if (retval == 0)
      printf("Thread %d finished with value %d\n",
	     i, *arg);
    else
      fprintf(stderr,"ERROR on join");
  }
  return 0;
}
This program creates five threads, and each thread goes through our loop which alternates between the NonCriticalSection and the CriticalSection. The Critical Section is guarded by a mutex. If you compile and run this program (don't forget to link with the pthread library by appending -lpthread to your compile statement), you should be able to confirm that only one thread at a time is in its critical section.

Semaphores in Unix

The pthread mutex works well for threads, but synchronization of multiple independent processes as created by fork is more complicated. Semaphores were not a part of the original Unix Operating Systems. They were added later, and the code is pretty ugly. This is a part of the Interprocess Communication (IPC) facility which includes mechanisms for shared memory and message passing in addition to semaphores.

The problem that IPC facilities have to solve is that two or more independent processes have to share a common structure, but it should not be available to just any old process. To obtain access to an IPC facility, the process has to know the key. The key is of type key_t but it is really just an integer. One process creates and initializes the semaphore, and other processes can then access it if they know the key.

The code for using IPC Semaphores is complicated, and, frankly, a little bizarre, and so we will not discuss it further. The interested student can read the Unix man pages for semget, semctl and semop.

Win32 Synchronization System Calls

Unlike Unix, where process synchronization seems to have been added as an afterthought, synchronization of both threads and processes is an inherent feature of the Windows operating systems, and Win32 provide a very rich set of synchronization APIs.

If all that you want to do is to increment, decrement or set a value for a variable which is shared between processes or threads, WIN32 provides the follow functions.

LONG InterlockedIncrement(LPLONG lpAddent);

LONG InterlockedDecrement(LPLONG lpAddend);

LONG InterlockedExchange(LPLONG target, LONG Value);
The first two of these take a pointer to an integer as an argument, and increment and decrement the value respectively. The last function sets the value of its first argument to the value of its second argument. These are guaranteed by the operating system to be atomic operations so the problem of an incorrect value resulting from a race condition when two or more processes or threads trying to update the variable at the same time cannot occur.

WIN32 allows independent processes to create and use a mutex in the usual fashion. A mutex has a handle and a name, which is a string. To create a new mutex, use this function

HANDLE CreateMutex(
    LPSECURITY_ATTRIBUTES lpMutexAttributes,
    BOOL bInitialOwer,
    LPCTSTR lpname
);
The first argument can be set to NULL, the second should be set to FALSE, and the third is the name of the mutex. This function returns a handle to the mutex.

Two or more processes can call CreateMutex to create the same named mutex. The first process actually creates the mutex and subsequent processes open a handle to the existing mutex.

The equivalent of the mutex lock facility is the API

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
which we have seen before. The first argument is the handle returned by the call to CreateMutex, the second is how long to wait before giving up. If the second argument is zero, this is equivalent to the pthread function pthread_mutex_trylock, i.e. it returns immediately. The return value will be either WAIT_OBJECT_0 which means that the mutex was successfully locked, or WAIT_TIMEOUT which means that the timeout occured before the mutex became available. If the second argument is set to INFINITE, the function is equivalent to the pthread function pthread_mutex_lock.

The function which is equivalent to pthread_mutex_unlock is

BOOL ReleaseMutex(HANDLE hMutex);

Here are links to the documentation for these functions
CreateMutex
OpenMutex
ReleaseMutex
WaitForSingleObject

Here's the exercise for the week