Sunday, November 4, 2018

Pthread - Passing multiple variables to thread start function

We have already covered basics of threads, and then created, joined and detached threads.

Now, will check how to pass single or multiple variables to the thread function.

Will be covering the following topics.



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

The Argument to a thread function?


The thread start function can take only one argument of type void *. It is passed as the fourth argument in pthread_create() function.



Since it's type is void *, any data type can be typecasted and passed to the thread function. In thread function, again, it is typecasted back to its original type.

Care should be taken when using the same variable in multiple threads. The argument is either pointer or reference to a variable. Any change in one thread will be reflected in other threads.

To avoid such effect, copy the initial value into another non-pointer local variable and make changes into that. Another way is to allocate new memory for the function arguments.

Use mutex or any other locks for synchronized access, if you want the changes in one thread to be seen into other threads.

What is the meaning of void * type?


The data type void *  is a void pointer that has no associated data type with it. It can hold the address of any type and can be typecasted to any type. So, can pass any data type to the function by typecasting it.

Let's play with the arguments


Example 1:
In this example, passing the same local variable to the three threads. The difference is how it is accessed in the thread function. In thread_1 and thread_2, using a pointer variable to access the thread function argument. In thread_3, using a non-pointer local variable. The variable is changed in thread_1, it is visible in thread_2 and parent.
  • thread_1: Used (int *) to access the variable and then changed its value.
  • thread_2: Used (int *) to access the variable. Changes in thread_1 are visible in thread_2 
  • thread_3: Used (int) to access the variable. Changes in thread_1 are not visible in thread_3. 
  • parent: Changes done in thread_1 are visible in the parent.


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

void *thread_1(void *a)
{
        int *b = (int*)a;
        printf("thread_1 : b = %d\n", *b);
        sleep(1);
        *b = 10;
        printf("thread_1 : Changed value to b = %d\n", *b);
        return NULL;
}

void *thread_2(void *a)
{
        int *b = (int*)a;
        printf("thread_2 : b = %d\n", *b);
        sleep(2);
        printf("thread_2 : Used pointer to input argument. Getting the effect of changes in thread_1 b = %d\n", *b);
        return NULL;
}

void *thread_3(void *a)
{
        int b = *((int*)a);
        printf("thread_3 : b = %d\n", b);
        sleep(2);
        printf("thread_3 : Used local non-pointer variable, no change seen here b = %d\n", b);
        return NULL;
}

void *parentFunction(void *arg)
{
        pthread_t th_1, th_2, th_3;
        pthread_attr_t attr;
        int rc;
        volatile int a = 5;

        if(rc = pthread_create(&th_1, NULL, thread_1, (void *)&a))
        {
                printf("Failed to create thread, Error returned %d\n", rc);
        }

        if(rc = pthread_create(&th_2, NULL, thread_2, (void *)&a))
        {
                printf("Failed to create thread, Error returned %d\n", rc);
        }

        if(rc = pthread_create(&th_3, &attr, thread_3, (void *)&a))
        {
                printf("Failed to create thread, Error returned %d\n", rc);
        }
        sleep(2);
        printf("parent : Passed reference to variable to the thread function, Got changed value a = %d\n", a);
        pthread_join(th_1, NULL);
        pthread_join(th_2, NULL);
        pthread_join(th_3, NULL);
        printf("parent : Joined with threads ... exiting now\n");
        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 ... exiting\n");
        return 0;
}

# Output of the above code

thread_1 : b = 5
thread_3 : b = 5
thread_2 : b = 5
thread_1 : Changed value to b = 10
parent : Passed reference to variable to the thread function, Got changed value a = 10
thread_2 : Used pointer to input argument. Getting the effect of changes in thread_1 b = 10
thread_3 : Used local non-pointer variable, no change seen here b = 5
parent : Joined with threads ... exiting now
Main thread: After joining parent thread ... exiting
                                                                         

This is happening because thread_1 and thread_2, the pointer used are pointing to the variable in the parent. Thus any change in thread_1 is visible in parent also. However, in thread_3, we are coping the variable to a non-pointer local variable. This variable is allocated in thread's stack. Any change to stack variable will not be visible to other threads.

How to pass multiple variables to a thread?

To pass multiple arguments to the thread function, create a structure to group all the arguments. Pass a pointer to the structure as an argument to the thread start_routine.

Example 2:
In this example, passing multiple arguments to thread start function. Used structure dataStruct to encapsulate all the variables and sent its reference to the thread function.



#include <stdio.h>
#include <pthread.h>
#define thread_count (4)

typedef struct dataStruct
{
        int start;
        int end;
}dataStruct;

void *Data_output(void *d)
{
        dataStruct *D = (dataStruct *)d;
        printf("Thread id %lu: Start time %d, End time %d\n", pthread_self(),
                D->start, D->end);
}

int main()
{
     pthread_t thread_id[4];
     dataStruct D[4] = {{100, 200}, {300, 500}, {600, 700}, {800, 900}};
     int i;
     for (i = 0; i < thread_count ; i++)
     {
        pthread_create (&thread_id[i], NULL, Data_output, (void *) &D[i]);
     }

     for (i = 0; i < thread_count; i++)
     {
        pthread_join (thread_id[i], NULL);

     }

     return (0);
}

// Output of the above code
Thread id 140476141180672: Start time 100, End time 200
Thread id 140476132787968: Start time 300, End time 500
Thread id 140476124395264: Start time 600, End time 700
Thread id 140476116002560: Start time 800, End time 900


Global variables with multiple threads?

While using global variables, any change in one thread will be visible in other threads. To get predictable results needs to synchronize its access. Mutex or any other locks are needed for synchronization. Will cover this in the mutex blog.