Saturday, October 27, 2018

Pthread - Lets create, join and detach threads


We have already covered basics of threads. Now will create a thread, join it and see how to detach it? While exploring each topic will answer the doubts, like ...

  • What is the lifetime of a thread?
  • Joinable and detached threads?
  • Why joining a thread is necessary?
  • Is there any way to avoid joining a thread?
  • What is the main thread, is it different than other threads?
  • Is there any relation between parent and child thread?
       etc ....

There are some important, good to know points in extra bytes.


Create a thread:

The function call pthread_create() creates a new thread for the process. The new thread starts executing start_routine.


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



What is the lifetime of a thread?

We know that a process lifetime is the main function. Similarly, a thread's lifetime is the start_routine. Once the thread returns from start_routine, it terminates.

Similar to a process, there are other ways to terminate a thread.
  • pthread_exit(): Called by default when a thread returns from start routine. However, pthread_exit() can be called explicitly to terminate a thread.
  • pthread_cancel(): A thread can be cancelled using function pthread_cancel().
  • exit(): All the threads of a process terminate if any thread calls exit(). Similarly, all threads terminate if main thread returns or exits.
  • Signal: If a signal's action is to terminate the process, all threads of the process terminate.


 After termination, a thread can be joined by another thread(pthread_join()).

Joinable and detached threads

 A thread is created as joinable or detached. A joinable thread needs to be joined after termination. Detached thread should not be joined.

By default, a thread is created as joinable. For creating a detached thread, need to set thread attribute using pthread_attr_setdetachstate() and attribute value PTHREAD_CREATE_DETACHED.

Why join a thread?

Each thread uses some resources to perform the given work. Along with that, some system resources are used to maintain threads. These system resources are released after thread termination. Failure to release causes resource leakage. So, who releases these resources?

When a thread is created, the ownership of there resources is given back to the process. So, any thread can release the resources by calling pthread_join() for the target thread.

The caller of pthread_join() also waits for the target thread termination if not already terminated.

In general, parent thread joins the child threads.

The return value or status from a thread is accessed via pthread_join().

Is there a way to avoid joining a thread and still release the resources?

Yes, if it's a detached thread. For a detached thread, the ownership of thread resources is transferred to the thread itself. Once a detached thread terminates, its resources are released.

A thread can be created as detached thread OR a joinable thread can be detached by a call to pthread_detach().

Once a thread is detached, cannot be made joinable again.

A thread can detach itself.


    pthread_detach(pthread_self());




It is important to either call pthread_join or pthread_detach() for a thread. However, not both.

Main thread and child threads

The thread created with the process is called the main thread. The main thread is the parent for all the threads. It is responsible for process related cleanup. When the main thread terminates, all other threads also terminate.

So, who can create a new thread?  The answer is, any thread can create a new thread. The thread hierarchy is not maintained by the process. Also, any thread can be joined by another thread. All the threads of a process are at the same level.




How to terminate the main thread but not other threads?

If the main thread is terminated using pthread_exit(), it will allow other threads to continue running. It keeps the main thread in zombie status. The drawback is you cannot get the return status of threads created by the main thread. Also, all process resources will be released when the process exits.

Example

In this example, we will create a parent thread and three child threads of type:
  • Joinable thread
  • Detached thread
  • A joinable thread which will be detached later.
We will see that even if parent exits, the child threads still continue to execute.

This shows that all the threads are peers and there is no parent-child relationship among them. The threads lifetime is not dependent on parent thread except if it is main thread. If main thread exits, all other threads also exit.


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

void *detachedFunction(void *)
{
        printf("detachedFunction %lu: Sleeping for 10 sec\n", pthread_self());
        sleep(10);
        printf("detachedFunction %lu: Came out of sleep\n", pthread_self());
        return NULL;
}

void *joinDetachedFunction(void *)
{
        pthread_detach(pthread_self());
        printf("joinDetachedFunction %lu: Sleeping for 10 sec\n", pthread_self());
        sleep(10);
        printf("joinDetachedFunction %lu: Came out of sleep\n", pthread_self());
        return NULL;
}

void *joinableFunction(void *)
{
        printf("joinableFunction %lu: Sleeping for 5 sec\n", pthread_self());
        sleep(5);
        printf("joinableFunction %lu: Came out of sleep\n", pthread_self());
        return NULL;
}

void *parentFunction(void *arg)
{
        pthread_t joinable, joinDetached, detached;
        pthread_attr_t attr;
        int rc;

        // Create a joinable thread
        if(rc = pthread_create(&joinable, NULL, joinableFunction, NULL))
        {
                printf("Failed to create thread, Error returned %d\n", rc);
        }

        // Create a joinable thread. We will detach it later
        if(rc = pthread_create(&joinDetached, NULL, joinDetachedFunction, NULL))
        {
                printf("Failed to create thread, Error returned %d\n", rc);
        }

        // Create a detached thread
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        if(rc = pthread_create(&detached, &attr, detachedFunction, NULL))
        {
                printf("Failed to create thread, Error returned %d\n", rc);
        }
        printf("parent %lu: Waiting to join joinable thread %lu\n", pthread_self(), joinable);
        pthread_join(joinable, NULL);
        printf("parent %lu: Joined with joinable thread %lu\n", pthread_self(), joinable);
        return NULL;
}


int main()
{
        pthread_t parent, joinable, detached1, detached2;
        int rc;
        if(rc = pthread_create(&parent, NULL, parentFunction, NULL))
        {
                printf("Failed to create thread, Error returned %d\n", rc);
                return rc;
        }

        pthread_join(parent, NULL);
        printf("Main thread: After joining parent thread\n");
        sleep(20);
        printf("Main thread: After sleep\n");
        return 0;
}


The output of the above program shows that all thread continue to run even when parent thread terminates. Here parent thread is not the main thread.


parent 140582397703936: Waiting to join joinable thread 140582389311232
joinDetachedFunction 140582380918528: Sleeping for 10 sec
detachedFunction 140582372525824: Sleeping for 10 sec
joinableFunction 140582389311232: Sleeping for 10 sec

joinDetachedFunction 140582380918528: Came out of sleep
detachedFunction 140582372525824: Came out of sleep
joinableFunction 140582389311232: Came out of sleep
parent 140582397703936: Joined with joinable thread 140582389311232
Main thread: After joining parent thread
Main thread: After sleep


What factors decide the maximum number of threads for a process?

In Linux, max limit can be found using command "cat /proc/sys/kernel/threads-max". However, there are other factors also which decide the maximum number of threads per process:
  • Virtual address space which needs to fit the stack for all threads.
  • Scheduler overhead. All the threads need to be scheduled. It should not make the system very slow.
  • Max number of processes for the system

How many parallel threads actually run in a system?

The number of threads running in a system depends on following:
  • Number of CPU
  • Number of core per CPU
  • Number of threads which can run per core.
Number of parallel threads = number of CPU * number of core per CPU * number of threads per core

In general 2 threads run per core. Use the lscpu command in Linux to get information on CPU attributes.


Extra bytes

  • PID and TID for a thread? All threads of the process have the same PID. Each thread has unique TID. TID is also called lwp or SPID. It is a unique number assigned by the kernel to identify a thread. TID is same as PID for the main thread.
  • How to pass different data type to the start routine? The start_routine takes only one argument of type (void *). We can typecast any pointer type into void * so we can pass any type to the thread start function. But only one parameter can be passed.
  • How to pass multiple arguments to a thread function? Use structure to encapsulate all augments you wish to pass to thread start function. Pass pointer to structure to the thread start function.
  • Who will execute first, caller thread or newly created thread? We cannot determine which thread will be scheduled first unless real-time scheduling is used.
  • Thread ID returned by pthread_self and gettid: gettid() returns the unique number assigned to a thread by the kernel. This is unique across processes. The ID returned by pthread_self() is assigned by the thread implementation. Depending on the implementation, it may be unique across the processes or a unique number for a process.
  • Thread idThe pthread_create() call returns the reference to the thread ID of type pthread_t. The ownership of the thread ID is with the process for a joinable thread. It is used to do release thread resources when pthread_join() is called on thread termination. For a detached thread, the ownership is transferred to the thread, so that it can release the resources. 
  • Calling pthread_join() for a detached thread: will try to release the resources twice and not calling pthread_join() for the joinable thread will cause resource leakage. This is the reason, a joinable thread should always be joined and a detached thread should not be joined. 
  • Is there a function to wait for any thread? The pthread library does not specify any function which will wait for any thread termination. For a child processwaitpid() with -1 as pid,  waits for any terminated child process. There is no such function for threads.

No comments:

Post a Comment