POSIX Threads | Pthread Tutorial for Beginners / Experienced

POSIX Threads 

What is the use of Pthread_join()? 

The pthread_join() function shall suspend execution of the calling thread until the target thread terminates unless the target thread has already terminated. On return from a successful pthread_join() call with a non-NULL value_ptr argument, the value passed to pthread_exit() by the terminating thread shall be made available in the location referenced by value_ptr. When a pthread_join() returns successfully, the target thread has been terminated. 

int pthread_join(pthread_t thread, void **value_ptr);

If successful, the pthread_join() function shall return zero; otherwise, an error number shall be returned to indicate the error.


What is Pthread_create()?

The pthread_create() function is used to create a new thread, with attributes specified by attr, within a process. If attr is NULL, the default attributes are used. If the attributes specified by attr are modified later, the thread's attributes are not affected. 

Upon successful completion, pthread_create() stores the ID of the created thread in the location referenced by a thread.

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

If successful, the pthread_create() function returns zero. Otherwise, an error number is returned to indicate the error.

The pthread_create() function starts a new thread in the calling
process. The new thread starts execution by invoking
start_routine(); arg is passed as the sole argument of
start_routine().

The attr argument points to a pthread_attr_t structure whose contents
are used at thread creation time to determine attributes for the new
thread; this structure is initialized using pthread_attr_init(3) and
related functions. If attr is NULL, then the thread is created with
default attributes.

pthread_exit()

pthread_exit() will exit the thread that calls it.
In your case since the main calls it, main thread will terminate whereas your spawned threads will continue to execute. This is mostly used in cases where the main thread is only required to spawn threads and leave the threads to do their job.
void pthread_exit(void *value_ptr);

pthread_exit terminates the calling thread while pthread_join suspends execution of calling thread until target threads completes execution.

pthread_mutex_lock():

int pthread_mutex_lock(pthread_mutex_t *mutex);

The mutex object referenced by mutex is locked by calling pthread_mutex_lock(). If the mutex is already locked, the calling thread blocks until the mutex become available. This operation returns with the mutex object referenced by mutex in the locked state with the calling thread as its owner.

 pthread_mutex_trylock()

int pthread_mutex_trylock(pthread_mutex_t *mutex);


The function pthread_mutex_trylock() is identical to pthread_mutex_lock() except that if the mutex object referenced by mutex is currently locked (by any thread, including the current thread), the call returns immediately.


pthread_mutex_unlock()

int pthread_mutex_unlock(pthread_mutex_t *mutex);

The pthread_mutex_unlock() function releases the mutex object referenced by mutex. The manner in which a mutex is released is dependent upon the mutex's type attribute

pthread_attr_init() 

The function pthread_attr_init() initializes a thread attributes object attr with the default value for all of the individual attributes used by a given implementation.

int pthread_attr_init(pthread_attr_t *attr);

The resulting attribute object (possibly modified by setting individual attribute values), when used by pthread_create(), defines the attributes of the thread created. A single attributes object can be used in multiple simultaneous calls to pthread_create().

pthread_attr_destroy()

The pthread_attr_destroy() function is used to destroy a thread attributes object. An implementation may cause pthread_attr_destroy() to set attr to an implementation-dependent invalid value. 

int pthread_attr_destroy(pthread_attr_t *attr);

The behavior of using the attribute after it has been destroyed is undefined.


When should we use asserts in C?

In general, asserts are for the programmer (i.e. you) to find logic/programming errors before releasing the program to real users. Asserts should not be used for detecting runtime input errors -- use error codes for these.

Implementaion of Pthread:

// mythread.h (A wrapper header file with assert
// statements)
#ifndef __MYTHREADS_h__
#define __MYTHREADS_h__
 
#include
#include
#include
 
void Pthread_mutex_lock(pthread_mutex_t *m)
{
    int rc = pthread_mutex_lock(m);
    assert(rc == 0);
}
                                                                                 
void Pthread_mutex_unlock(pthread_mutex_t *m)
{
    int rc = pthread_mutex_unlock(m);
    assert(rc == 0);
}
                                                                                 
void Pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
           void *(*start_routine)(void*), void *arg)
{
    int rc = pthread_create(thread, attr, start_routine, arg);
    assert(rc == 0);
}
 
void Pthread_join(pthread_t thread, void **value_ptr)
{
    int rc = pthread_join(thread, value_ptr);
    assert(rc == 0);
}
 
#endif // __MYTHREADS_h__


monitors

monitor ProducerConsumer 
{
    int itemCount = 0;
    condition full;
    condition empty;

    procedure add(item) 
    {
        if (itemCount == BUFFER_SIZE) 
        {
            wait(full);
        }

        putItemIntoBuffer(item);
        itemCount = itemCount + 1;

        if (itemCount == 1) 
        {
            notify(empty);
        }
    }

    procedure remove() 
    {
        if (itemCount == 0) 
        {
            wait(empty);
        }

        item = removeItemFromBuffer();
        itemCount = itemCount - 1;

        if (itemCount == BUFFER_SIZE - 1) 
        {
            notify(full);
        }


        return item;
    }
}

procedure producer() 
{
    while (true) 
    {
        item = produceItem();
        ProducerConsumer.add(item);
    }
}

procedure consumer() 
{
    while (true) 
    {
        item = ProducerConsumer.remove();
        consumeItem(item);
    }
}


Bounded Buffer Problem (Producer/Consumer Problem) 

Information common to both processes:
empty := n
full := 0
mutex := 1 

Producer Process

repeat
produce an item in nextp 

wait(empty); 
wait(mutex); 

add nextp to buffer 

signal(mutex); 
signal(full);
until false;

Consumer Process
repeat
wait(full); 
wait(mutex); 

remove an item from buffer to nextc 

signal(mutex); 
signal(empty); 

consume the item in nextc
until false;


Readers/Writers Problem

http://jcsites.juniata.edu/faculty/rhodes/os/ch5d.htm



Advantages of Thread over Process;
  1. Responsiveness
  2. Faster context switch
  3. Effective Utilization of Multiprocessor system
  4. Resource sharing
  5. Communication
  6. Enhanced Throughput of the system

1. Responsiveness: If the process is divided into multiple threads, if one thread completed its execution, then its output can be immediately responded.
2. Faster context switch: Context switch time between threads is less compared to process context switch. Process context switch is more overhead for CPU.
3. Effective Utilization of Multiprocessor system: If we have multiple threads in a single process, then we can schedule multiple threads on multiple processors. This will make process execution faster.
4. Resource sharing: Resources like code, data, and file can be shared among all threads within a process.
Note: stack and registers can’t be shared among the threads. Each thread has its own stack and registers.
5. Communication: Communication between multiple threads is easier as thread shares common address space. while in a process we have to follow some specific communication technique for communication between two processes.
6. Enhanced Throughput of the system: If a process is divided into multiple threads and each thread function is considered as one job, then the number of jobs completed per unit time is increased. Thus, increasing the throughput of the system.


Comments