Thursday 6 September 2012

Pthread Library Part:1

Prerequisite: You may required to read my previous post on threads, because i will be referencing that in this post.

Pthreads, short name for POSIX Threads, is a thread library developed by the POSIX standard by IEEE. This library provides an interface to create threads in a program.

All the threads created using pthread library are purely KERNEL threads. So, what are these kernel threads? These are the threads that run directly on the kernel and Linux kernel especially doesn't actually differentiate between processes and threads. Hence, the threads are directly scheduled by the operating system, along with the other processes.

Why to learn this?

So, you want to write a program using pthreads. But why do you actually need to do that? The main reason is that writing different programs using pthreads especially the Classical Inter Process Communication problems gives you a good idea about the various issues related to parallel processing which in turn helps you visualize what actually operating system does to solve these issues.

Having known the main reason of why to write these programs, let us jump into the library and explore different types of functions and their uses.

Exploring the library

First things first, when you use a pthread library,you should include the pthread.h header file.

#include < pthread.h > 

pthread_t:

A thread object is denoted by pthread_t. This is the type name for threads in a pthread library.


pthread_create:

In order to create a thread, we use the function pthread_create and its prototype is as follows:

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

As we see the prototype, it takes four arguments, the first one being a pointer to a pthread_t object, second one will be discussed later, for now put it as NULL

Third argument is the function pointer to the function which you want to execute using this thread. If you closely observe the function should take void* type arguments and return a void pointer. But in general, we just write the functions which are of void type(to avoid confusion) and either take no arguments or a single void pointer. This function is type casted to void pointer while passing to the above function

The fourth one is the argument which you want to pass to the called function. It should be type casted to void pointer. In the function, it should be casted back to its original type and use its value.

The above function creates a thread and this thread executes the function which is passed as argument, and of course, the main method is executed parallelly, and the thread gets terminated once it has finished executing the given function.


pthread_join:

As said above, since the main thread executes parallelly along with the newly created thread, when the main thread is terminated it also kills the created thread due to which we can't see the action of the thread. There should be some mechanism to block the main thread until the created thread completes its execution. Here comes the pthread_join method with the following prototype:

int pthread_join(pthread_t thread, void **retval)

It takes two arguments, the name of thread for which the calling thread has to wait, In the case you call this function from main, the main thread waits for the thread specified by the argument.

The second argument is a double pointer which contains the exit status of the terminated thread, i.e., whether it exited normally or due to some other signal. This is not quite oftenly used and can be kept NULL.


We are done with the main functions required to create and run a thread. Now, let us write a simple program which creates two threads, that prints some strings.

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

void fun1()
{
    printf("this is from thread 1!\n");

}
void fun2()
{
     printf("this is from thread 2!\n");
}
int main(int argc, char*argv[])
{
     pthread_t t1,t2;
     pthread_create(&t1, NULL, (void*)&fun1, NULL);
     pthread_create(&t2, NULL, (void*)&fun2, NULL);
     pthread_join(t1, NULL);
     pthread_join(t2, NULL);
 exit(0);
}

Compiling the above program in Linux can be done by adding -pthread argument along with normal cc file_name.c. If you are using Solaris, then use -lpthread instead.

When you run the program, you will see two strings on the console, of course the order may change based on the scheduling of the threads, CPU load and other factors.


The above program is a trivial one and they don't actually have any shared data. But when two threads actually try to manipulate a common variable, like incrementing or decrementing the value, since those operations are not atomic, i.e., they are not executed at once by the processor, for example incrementing a value involves single instruction in C/C++:

variable_name++

But, when this is converted to machine instructions, there exists three instructions as follows:

MOV register, [addres_of_the_variable]
INC register
MOV [addres_of_the_variable], register

Speaking truly, the computer cannot guarantee that all these instructions are executed simultaneously, but there will be a chaos when these instructions are mixed up with decrement instructions of the same variable.

Hence, two threads executing parallelly, should never change the value of a shared variable at the same time, because we are not aware which instruction executes first, the value is unpredictable. This is called Race Condition.

This should be strictly avoided and there comes the MUTEX into the picture.


Mutex:

It is the short form of Mutual Execution. The code which manipulates the shared variable among the threads is called Critical section. Mutexes can be used to execute this critical section of code, exactly one thread at a time, thus avoiding the Race condition.

Let us look how this is accomplished...


pthread_mutex_t:

This is the type name of mutexes in pthread library.


pthread_mutex_init:

This is the function which initializes the mutex. It has the following prototype:

int pthread_mutex_init(pthread_mutex_t *mutex, 
    const pthread_mutexattr_t *attr)

This function takes two arguments. The first one is a pointer to the mutex and second one is the mutex attributes. We normally keep the second argument NULL.


pthread_mutex_lock:

This function is used to lock the mutex. You can imagine this as follows. Every mutex has a 'key' and when any thread calls the lock function, this key is given to the calling thread. The prototype is as follows:

int pthread_mutex_lock(pthread_mutex_t *mutex)

The only argument is the pointer to the mutex variable.


pthread_mutex_unlock:

This function is used to unlock the previously locked mutex. If you try to call this without gettng the lock of the mutex, consequently you will get an execption. You can imagine that this method actually gives back the 'key' to the mutex. The prototype is as follows:

int pthread_mutex_unlock(pthread_mutex_t *mutex)

This also takes a single argument of mutex pointer type.


I know that you are bit confused with this 'lock' thing. Let us put together what happens.

Two threads having a shared variable consequently have critical section in their function code. Our aim is that only one of the threads executes this code. So, just define a mutex and initialize it. Then, call lock function in both the threads' functions before the critical section and unlock function after the critical section.

Suppose, thread A got the 'key' and thread B also called the lock function, but since our mutex has only one key and its with thread A, thread B has to wait. When thread A returns the 'key' after executing the critical section the mutex has the key and it gives it to thread B. Now, thread B starts executing, and if thread A wants again to execute the code , it has to wait until B returns the 'key'.

Voila! what we get from the above situation is only one thread is executing the critical code, satisfying the mutual exclusion principle and avoiding the Race Condition.


pthread_mutex_trylock:

As I said before, a thread gets blocked when it calls a lock function but the 'key' is with other thread. Sometimes we don't want a thread to wait for the 'key' but just check if a mutex is locked and get the 'key' if the mutex has the key. Then try_lock function comes handy.The prototype is as follows:

int pthread_mutex_trylock(pthread_mutex_t *mutex)

If the mutex has the 'key' the thread gets it, but doesn't wait if it doesn't possess the key.


Now, let us look at a simple program to prove mutual exclusion. I am not using any shared varaibles here, instead i print some strings.

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

pthread_mutex_t J;

void fun1()
{
while(1)
{
   pthread_mutex_lock(&J);
   printf("1\n");
   printf("2\n");
   pthread_mutex_unlock(&J);
   sleep(1);
}
}
void fun2()
{
while(1)
{
   pthread_mutex_lock(&J);
   printf("3\n");
   printf("4\n");
   pthread_mutex_unlock(&J);
   sleep(2);
}
}
void main(int argc, char* argv[])
{
    pthread_t A,B;
    pthread_mutex_init(&J, NULL);
    pthread_create(&A, NULL, (void*)&fun1, NULL);
    pthread_create(&B, NULL, (void*)&fun2, NULL);
    pthread_join(A, NULL);
exit(0);
}

If you observe the above program, I have used sleep function so that we will be able to see the output, and more over if you observe the output you can see that 1&2 always are adjacent and 3&4 are always adjacent.

You can't have the output like 1324... because of the mutexes.Thus ensuring the Mutual Exclusion. The same can be applied to shared variables also.


<<Prev Next>>

4 comments:

  1. hey nitish, does this implementation takes care of the ordering of function execution? I mean func1 should execute before func2?

    ReplyDelete
    Replies
    1. No, that entirely depends which thread will get the cpu first. If ordering matters, then using pthreads is meaningless. If you still like to use pthreads, to get an ordering, use mutexes :)

      Delete