Introduction to Operating System

Lab3: Threads


Lab Overview

This lab will acquaints you with threading basics. The threading system will be used is the standard pthreads package. pthreads is standard on Unix/Linx systems, and because C doesn't contain any intrinsic threading primitives, pthreads is just a library of fucntions that interfaces to OS syscalss for creating and destroying threads. Because pthreads is a separate library, when use gcc, -lpthreads flag must be used. For example, if your source file is named thread.c, then you have to complie it so:

gcc thread.c -o thread -lpthread
Additionally, you will need to include the pthreads header file pthread.h.

Tasks:

1. pthread_create

In pthreads, new threads are created by calling the function pthread_creae, the actual definition of pthread_create is quite verbose:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void),void *arg);
The first argument is a pointer to a pthread_t structure. This structure holds all kinds of useful information about a pthread (mostly used by the threading internals). So, for each thread you create, you'll need to allocate one such structure to store all its information. the second argument is a point to a pthread_attr_t struct. The structure contains the attributes of the thread. This has even more information about the thread. The only really interesting thing, is that the pthread_attr_t struct tells the system how much memory to allocate for the thread's stack. By default, this is usually 512K. This is actually very large, and if you run with lots of threads you can run out of memory.Therefore, you can initialize an attribute structure and set the stack size, like so:
#define SMALL_STACK 131072 //128K

pthread_attr_t thread_attr;

pthread_attr_init(&thread_attr);
pthread_attr_setstacksize(&thread_attr,SMALL_STACK);

The third argument to pthread_create is even more mysterious. void * (*start_routine)(void *). This is actually the function that the thread should start executing. This argument is an instance of what C programmers call a function pointer. The name of the argument is start_routine, and it is a function that returns void * and accepts argument of type void *.

The last argument to pthread_create is a void* .why is void *? Because in C, void * basically means anything. You can cast pointers, ints, chars, etc. to a void *. Which means that it is really the only way to specify a generic argument in C.

An Example

Here is an example, which creates a thread that executes the function fn.

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>

#define SMALL_STACK 131072

pthread_attr_t thread_attr;

void * fn(void* arg);

int main(){

	pthread_attr_init(&thread_attr);
	pthread_attr_setstacksize(&thread_attr,SMALL_STACK);

	pthread_t th;

	pthread_create(&th, &thread_attr, fn, (void *)14);

	void * val;
	pthread_join(th,&val);
	printf("This is parent!\n");

	return 0;
}

	void * fn(void* arg){
		printf("arg=0x%x\n",(int)arg);
		return NULL;
	}

pthread_join call makes the main thread wait until the child terminates (like a waitpid for threads). This code performs all the attribute initialization, and then creates a single child thread that starts executing the fn function. Notice the cast of 14 into a void *, it looks weird, but that is how you do it in pthreads!


Q1:Remove the call to phtread_join in the code above. Now recomplie and run it several times. Does it always print the same thing? Why or why not?.

2.Mutexes and Condition Variables

pthreads supports explicit mutexes and condition variables. Mutexes are fairly simple. A pthread mutex has the type pthred_mutex_t. And they must be initialized by the pthread_mutex_init function.Following is an example:

pthread_mutex_t mut;
pthread_mutex_init(&mut,NULL);

The 2nd argument to the init routine is NULL because that specifies the mutex attributes to use the defaults. pthreads mutexes are simple, lock them with pthread_mutex_lock function and unlock them with pthread_mutex_unlock function.

3.Condition Variables

phtreads also supports condition variables.A pthread condition variable is stored in a pthread_condition_t variable that must be initiallized by a call to pthread_cond_init, like so:

pthread_cond_t my_cond;
pthread_cond_init(&my_cond,NULL);
pthread condition variables can be waited on, signaled to (wake up one waiting thread), broadcast to(wake up all waiting threads).

The function to wait on a condition variable is called pthread_cond_wait and its signature is:

int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
The first argument is just the condition to wait on, but the second argument is a mutex. Why is there a mutex involved? The call to pthread_cond_wait puts the thread to sleep and unlocks the mutex. This is necessary to prevent deadlock(a sleeping thread can't unlock mutexes without waking up). When the thread awakes (the call to pthread_cond_wait returns), it is guaranteed to be holding the mutex again. The mutex argument ensures that the programmer con't instantly deadlock her program by forgetting to unlock the mutex before calling pthread_cond_wait. Conditions are signaled by pthread_cond_signal, which takes a pointer to a condition as its one and only aurgument. In pthreads, signalling ensures that the awakened thread will hold the mutex(essentially, signal puts the thread on the wait queue for the mutex). This leads to the following sequence of actions for using a condition:
Thread1Thread2
lock mutex;
wait on cond,mutex
waiting on cond,mutex
waiting on cond,mutex
waiting on mutex
re-lock mutex
lock mutex;(block waiting for thread1)
lock mutex
do some work
signal cond
unlock mutex

Note the order:

1. Lock before you wait(and wait on a mutex you locked)
2. Signal before you unlock
Try 2-32 in Chapter 2, try to understand how the locks work.

Helpful Documents


Student Feedbacks

If you have any advice and sugguestions about the lab or the course design, please let me know.:)