CS604 - Operating Systems - Lecture Handout 13

User Rating:  / 0
PoorBest 

Related Content: CS604 - VU Lectures, Handouts, PPT Slides, Assignments, Quizzes, Papers & Books of Operating Systems

Summary

  • User- and Kernel –level threads
  • Multi-threading models
  • Solaris 2 threads model
  • POSIX threads (the pthread library)
  • Sample code

User and Kernel Threads

Support for threads may be provided at either user level for user threads or by kernel for kernel threads.

User threads are supported above kernel and are implemented by a thread library at the user level. The library provides support for thread creation, scheduling, and management with no support from the kernel. Since the kernel is unaware of user-level threads, all thread creation and scheduling are done in the user space without the need for
kernel intervention, and therefore are fast to create and manage. If the kernel is single threaded, then any user level thread performing a blocking system call will cause the entire process to block, even if other threads are available to run within the application.
User thread libraries include POSIX Pthreads , Solaris 2 UI-threads, and Mach Cthreads.

Kernel threads are supported directly by the operating system. The kernel performs the scheduling, creation, and management in kernel space; the kernel level threads are hence slower to create and manage, compared to user level threads. However since the kernel is managing threads, if a thread performs a blocking system call, the kernel can schedule another thread in the application for execution. Windows NT, Windowss 2000, Solaris, BeOS and Tru64 UNIX support kernel threads.

Multi-threading Models

There are various models for mapping user-level threads to kernel-level threads. We describe briefly these models, their main characteristics, and examples.

  1. Many-to-One: In this model, many user-level threads are supported per kernel thread, as shown in Figure 13.1. Since only one kernel-level thread supports many user threads, there is no concurrency. This means that a process blocks when a thread makes a system call. Examples of these threads are Solaris Green threads POSIX Pthreads. Many –to-One Model
  2. One-to-One: In this model, there is a kernel thread for every user thread, as shown in Figure 13.2. Thus, this model provides true concurrency. This means that a process does not block when a thread makes a system call. The main disadvantage of this model is the overhead of creating a kernel thread per user thread. Examples of these threads are WindowsNT, Windows 2000, and OS/2.One-to-One Model
  3. Many-to-One: In this model, multiple user-level threads are multiplexed over a smaller or equal number of kernel threads, as shown in Figure 13.2. Thus, true concurrency is not achieved through this model. Examples of these threads are Solais 2 and HP-UX.Many-to Many Model

Solaris 2 Threads Model

Solaris 2 has threads, lightweight processes (LWPs), and processes, as shown in Figure 13.4. At least one LWP is assigned to every user process to allow a user thread to talk to a kernel thread. User level threads are switched and scheduled among LWPs without kernel’s knowledge. One kernel thread is assigned per LWP. Some kernel threads have no LWP associated with them because these threads are not executed for servicing a request by a user-level thread. Examples of such kernel threads are clock interrupt handler, swapper, and short-term (CPU) shceduler.

Solaris 2 Threads Model

POSIX Threads (the pthread library)

Pthreads refers to the POSIX standard defining an API for thread creation, scheduling, and synchronization. This is a specification for thread behavior not an implementation.
OS designers may implement the specification in any way they wish. Generally, libraries implementing the Pthreads specification are restricted to UNIX-based systems such as Solaris 2. In this section, we discuss the Pthreads library calls for creating, joining, and terminating threads and use these calls to write small multi-threaded C programs.

Creating a Thread

You can create a threads by using the pthread_create() call. Here is the syntax of this call.

int pthread_create(pthread_t *threadp, const pthread_attr_t *attr,
void* (*routine)(void *), arg *arg);

where, ‘threadp’ contains thread ID (TID) of the thread created by the call, ‘attr’ is used to modify the thread attributes (stack size, stack address, detached, joinable, priority, etc.), ‘routine’ is the thread function, and ‘arg’ is any argument we want to pass to the thread function. The argument does not have to be a simple native type; it can be a ‘struct’ of whatever we want to pass in.
The pthread_create() call fails and returns the corresponding value if any of the following conditions is detected:

  • EAGAIN The system-imposed limit on the total number of threads in a process has been exceeded or some system resource has been exceeded (for example, too many LWPs were created).
  • EINVAL The value specified by ‘attr’ is invalid.
  • ENOMEM Not enough memory was available to create the new thread.

You can do error handling by including the <errno.h> file and incorporating proper error handling code in your programs.

Joining a Thread

You can have a thread wait for another thread within the same process by using the pthread_join() call. Here is the syntax of this call.

int pthread_join(pthread_t aThread, void **statusp);

where, ‘aThread’ is the thread ID of the thread to wait for and ‘statusp’ gets the return value of pthread_exit() call made in the process for whom wait is being done.
A thread can only wait for a joinable thread in the same process address space; a thread cannot wait for a detached thread. Multiple threads can join with a thread but only one returns successfully; others return with an error that no thread could be found with the given TID

Terminating a Thread

You can terminate a thread explicitly by either returning from the thread function or by using the pthread_exit() call. Here is the syntax of the pthread_exit() call.
void pthread_exit(void *valuep);
where, ‘valuep’ is a pointer to the value to be returned to the thread which is waiting for this thread to terminate (i.e., the thread which has executed pthread_join() for this thread).
A thread also terminates when the main thread in the process terminates. When a thread terminates with the exit() system call, it terminates the whole process because the purpose of the exit() system call is to terminate a process and not a thread.

Sample Code

The following code shows the use of the pthread library calls discussed above. The program creates a thread and waits for it. The child thread displays the following message on the screen and terminates.

Hello, world! ... The threaded version.

As soon as the child thread terminates, the parent comes out of wait, displays the following message and terminates.

Exiting the main function.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
/* Prototype for a function to be passed to our thread */
void* MyThreadFunc(void *arg);
int main()
{
pthread_t aThread;
/* Create a thread and have it run the MyThreadFunction */
pthread_create(&aThread, NULL, MyThreadFunc, NULL);
/* Parent waits for the aThread thread to exit */
pthread_join(aThread, NULL);
printf ("Exiting the main function.\n");
return 0;
}
void* MyThreadFunc(void* arg)
{
printf ("Hello, world! ... The threaded version.\n");
return NULL;
}

The following session shows compilation and execution of the above program. Does the output make sense to you?

$ gcc hello.c –o hello –lpthread –D_REENTRANT
$ hello
Hello, world! ... The threaded version.
Exiting the main function.
$

Note that you need to take the following steps in order to be able to use the pthread library.

  1. Include <pthread.h> in your program
  2. Link the pthread library with your program (by using the –lpthread option in the compiler command)
  3. Pass the _REENTRANT macro from the command line (or define it in your program)

Here is another program that uses the pthread library to create multiple threads and have them display certain messages. Read through the code to understand what it does.
Then compile and run it on your UNIX/Linux system to display output of the program and to see if you really understood the code.

/**********************************************************************
* FILE: hello_arg2.c
* DESCRIPTION:
* A "hello world" Pthreads program which demonstrates another safe way
* to pass arguments to threads during thread creation. In this case,
* a structure is used to pass multiple arguments.
*
* LAST REVISED: 09/04/02 Blaise Barney
**********************************************************************/
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 8

char *messages[NUM_THREADS];

struct thread_data
{
int thread_id;
int sum;
char *message;
};

struct thread_data thread_data_array[NUM_THREADS];

void *PrintHello(void *threadarg)
{
int taskid, sum;
char *hello_msg;
struct thread_data *my_data;

sleep(1);
my_data = (struct thread_data *) threadarg;
taskid = my_data->thread_id;
sum = my_data->sum;
hello_msg = my_data->message;
printf("Thread %d: %s Sum=%d\n", taskid, hello_msg, sum);
pthread_exit(NULL);
}

int main(int argc, char *argv[])
{

pthread_t threads[NUM_THREADS];
int *taskids[NUM_THREADS];
int rc, t, sum;

sum=0;
messages[0] = "English: Hello World!";
messages[1] = "French: Bonjour, le monde!";
messages[2] = "Spanish: Hola al mundo";
messages[3] = "Klingon: Nuq neH!";
messages[4] = "German: Guten Tag, Welt!";
messages[5] = "Russian: Zdravstvytye, mir!";
messages[6] = "Japan: Sekai e konnichiwa!";
messages[7] = "Latin: Orbis, te saluto!";

for(t=0; t<NUM_THREADS; t++) {
sum = sum + t;
thread_data_array[t].thread_id = t;
thread_data_array[t].sum = sum;
thread_data_array[t].message = messages[t];
printf("Creating thread %d\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello, (void *) &thread_data_array[t]);
if (rc) {
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
pthread_exit(NULL);
}

Reference

The above code was taken from the following website.

http://www.llnl.gov/computing/tutorials/pthreads/samples/hello_arg2.c