Go4Expert

Go4Expert (http://www.go4expert.com/)
-   C (http://www.go4expert.com/articles/c-tutorials/)
-   -   Thread Synchronization using Mutex (http://www.go4expert.com/articles/thread-synchronization-using-mutex-t26730/)

poornaMoksha 18Sep2011 20:00

Thread Synchronization using Mutex
 
Continuing the previous discussion that we did on Unix Threads (Basics) , today we will discuss the concept of 'Thread Synchronization'. Going through a quick recap, we now know that threads are used to make calls to functions non-blocking (asynchronous). One important point that we did not discuss there was that all the threads use the same memory and variables of the program. For example, if we have a global variable 'count' that is used in function 'func()' and this function is used by all the threads then all the threads share the same variable. This brings a big problem for the programmer.

As the programmer has no control over the order of execution of threads, then while a thread is being executed and it is changing the value of this global variable and suddenly the OS brings some other thread into execution which tries to read value of the same global variable then this thread may read some garbage/wrong/undesired value of this global variable. So programmer needs to make sure that such critical areas of code are executed by threads in a synchronous way. Synchronous here means that if a thread comes to execute a critical area (like reading/writing a global variable) then any other thread should not be allowed to enter this critical area until the previous thread is done executing all the critical statements.

Example



An example would make it more clear. Consider the following peice of code :
Code: C

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

int counter;

void* doSomeTimConsumingStuff(void* buff)
{
    /** Simulating some time consuming stuff **/
    unsigned long i = 0;

    counter = counter+1;
    printf("\n [%d] request(s) queued\n",counter);

    for(i=0; i<(0xFFFFFFFF);i++);

    printf("\n [%d] request(s) done",counter);
    printf("\n ----------------------------------------> Mr/Ms %s, your work done\n", (char*)buff);


    free(buff);
    return NULL;
}

int main(void)
{
    int i = 0;
    int j = 0;
    pthread_t tid[5];
    int err;
    void* tret = NULL;
    char* names[] = {"abc","def","ghi","jkl","mno"};


    while(i < 5)
    {
        char *buff = (char*)malloc(100);
        memset(buff,'0',sizeof(100));

        //printf("\n Enter your name :");
        //scanf("%s",buff);

        strncpy(buff,names[i],100);

        err = pthread_create(&(tid[i]), NULL, &doSomeTimConsumingStuff, buff);
        if (err != 0)
            printf("\ncan't process your request: %s\n", strerror(err));
        else
            printf("\n Thanks Mr/Ms [%s] for the request, your request is under processing, will notify you when its done\n", buff);

        i++;
    }
    for(j = 0;j<5;j++)
    {
        err = pthread_join(tid[j], &tret);
        if (err != 0)
            printf("\n can't process the request: %s\n", strerror(err));
    }

    return 0;
}

In the above code we see that '5' threads are created. All the 5 threads enter the function 'doSomeTimConsumingStuff()' and execute the line 'counter = counter+1' 5 times. So when the first thread who is done processing the actual logic (the for loop) tries to print the value of counter to know how many requests are completed, the value comes out to be '5' (see output in red below) but ideally the value should have been '1' as only one request is completed at this point of time and should have incremented as the corresponding threads completed.

The output of the code justifies the above stated explanation :
Code:

Thanks Mr/Ms [abc] for the request, your request is under processing, will notify you when its done

 [1] request(s) queued

 [2] request(s) queued

 Thanks Mr/Ms [def] for the request, your request is under processing, will notify you when its done

 Thanks Mr/Ms [ghi] for the request, your request is under processing, will notify you when its done

 Thanks Mr/Ms [jkl] for the request, your request is under processing, will notify you when its done

 Thanks Mr/Ms [mno] for the request, your request is under processing, will notify you when its done

 [3] request(s) queued

 [4] request(s) queued

 [5] request(s) queued

[5] request(s) done
 ----------------------------------------> Mr/Ms jkl, your work done

[5] request(s) done
 ----------------------------------------> Mr/Ms def, your work done

[5] request(s) done
 ----------------------------------------> Mr/Ms abc, your work done

 [5] request(s) done
 ----------------------------------------> Mr/Ms mno, your work done

[5] request(s) done
 ----------------------------------------> Mr/Ms ghi, your work done

This happened because all the threads were allowed executing the function 'doSomeTimConsumingStuff()' without making sure that the previous thread was done with this function or not. So here we reproduced a synchronization problem with multiple threads.

This problem can be taken care by using a thread synchronization mechanism known as mutexes. By using mutexes we can prevent a thread from entering and executing a critical piece of code when a previous thread is already in middle of executing this critical piece of code.

Lets try it through an example. Consider the following piece of code :

Code: c

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

int counter;
pthread_mutex_t lock;

void* doSomeTimConsumingStuff(void* buff)
{
    /** Simulating some time consuming stuff **/
    unsigned long i = 0;

    pthread_mutex_lock(&lock);

    counter = counter+1;
    printf("\n [%d] request(s) queued\n",counter);

    for(i=0; i<(0xFFFFFFFF);i++);

    printf("\n [%d] request(s) done",counter);
    printf("\n ----------------------------------------> Mr/Ms %s, your work done\n", (char*)buff);


    free(buff);
    pthread_mutex_unlock(&lock);
    return NULL;
}

int main(void)
{
    int i = 0;
    int j = 0;
    pthread_t tid[5];
    int err;
    void* tret = NULL;
    char* names[] = {"abc","def","ghi","jkl","mno"};

    if (pthread_mutex_init(&lock, NULL) != 0)
    {
        printf("\n mutex init failed\n");
        return 1;
    }

    while(i < 5)
    {
        char *buff = (char*)malloc(100);
        memset(buff,'0',sizeof(100));

        //printf("\n Enter your name :");
        //scanf("%s",buff);

        strncpy(buff,names[i],100);

        err = pthread_create(&(tid[i]), NULL, &doSomeTimConsumingStuff, buff);
        if (err != 0)
            printf("\ncan't process your request: %s\n", strerror(err));
        else
            printf("\n Thanks Mr/Ms [%s] for the request, your request is under processing, will notify you when its done\n", buff);

        i++;
    }
    for(j = 0;j<5;j++)
    {
        err = pthread_join(tid[j], &tret);
        if (err != 0)
            printf("\n can't process the request: %s\n", strerror(err));
    }
    return 0;
}

What we did is, we used mutex over the critical piece of code in the function 'doSomeTimConsumingStuff()' to prevent any other thread from entering into the code if some previous thread locked this mutex but hasn't unlocked it yet. The output of the above code falls in line with what exactly is expected from it :

Code:

Thanks Mr/Ms [abc] for the request, your request is under processing, will notify you when its done

 [1] request(s) queued

 Thanks Mr/Ms [def] for the request, your request is under processing, will notify you when its done

 Thanks Mr/Ms [ghi] for the request, your request is under processing, will notify you when its done

 Thanks Mr/Ms [jkl] for the request, your request is under processing, will notify you when its done

 Thanks Mr/Ms [mno] for the request, your request is under processing, will notify you when its done

 [1] request(s) done
 ----------------------------------------> Mr/Ms abc, your work done

 [2] request(s) queued

 [2] request(s) done
 ----------------------------------------> Mr/Ms def, your work done

 [3] request(s) queued

 [3] request(s) done
 ----------------------------------------> Mr/Ms mno, your work done

 [4] request(s) queued

 [4] request(s) done
 ----------------------------------------> Mr/Ms ghi, your work done

 [5] request(s) queued

 [5] request(s) done
 ----------------------------------------> Mr/Ms jkl, your work done

So, in the above output, we can clearly see that all the threads access the function 'doSomeTimConsumingStuff()' in a synchronized way so that the value of the variable 'counter' is not messed up(as we saw in the output of first piece of code).

Conclusion



Kernel can bring any thread into execution depending upon its algorithm. Programmer has no control over it. What best we can do if we are developing a multi-threaded application is to use mutexes wherever we know synchronized access of threads is required. This will help create a good multi-threaded application which would be easy to manage.

Stay tuned for more....


All times are GMT +5.5. The time now is 21:51.