Friday, December 7, 2012

Multithreading - Part 2: Synchronization


Consider the program which we mentioned in "Multithreading - Part 1: Basics"


Program:

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


int gvar;

void* t1_fun(void *msg)
{
    int i;
    for(i = 0; i < 10; i++)
    {   
        gvar++;
        /*printf("[%d:%s] : gvar = %d.\n", pthread_self(), (char*) msg, gvar);*/
        printf("[%d:%s] : gvar = %d.\n", (long int)syscall(224), (char*) msg, gvar);
    }   
}

int main()
{
    pthread_t t1, t2; 
    char *msg1 = "thread1";
    char *msg2 = "thread2";

    int rc1 = pthread_create(&t1, NULL, t1_fun, (void *) msg1);
    if( rc1 ) { printf("Error for thread 1.\n"); perror("pthread_create"); }
    int rc2 = pthread_create(&t2, NULL, t1_fun, (void *) msg2);
    if( rc2 ) { printf("Error for thread 1.\n"); perror("pthread_create"); }

    /* If the 2nd arg is NOT NULL, the return value of t1 and t2 will be stored there.
     * This pthread_join will suspend the main thread until the t1/t2 completes, terminates or by calling
     * pthread_exit. 
     */
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);    
    printf("Threads execution over.\n");
    return 0;
}

NOTE that if there is no load in the system, this program might print the value of "gvar" as 20 as expected (each thread increments gvar 10 times; hence 20).
But, if you increase the load in the system or if you increase the count in the for loop (replace the count 10 with 50), you can see the threads will fight each other to increment the global variable "gvar". At the  end of the program, if you see, the "gvar" will not be "100" as expected; but rather less (see below).


[5164:thread1] : gvar = 95.
[5164:thread1] : gvar = 96.
...
...
[5165:thread2] : gvar = 95.
[5165:thread2] : gvar = 97.


This kind of situation is called "race condition" where two or more threads will race with each other to change the state of a variable without bothering about the other one.

To avoid this, we have to use mutex.

Mutex (ONLY BETWEEN THREADS IN A SINGLE PROCESS; NOT ACROSS PROCESSES LIKE SEMAPHORE):
Mutexes are used to prevent data inconsistencies due to operations by multiple threads upon the same memory area performed at the same time or to prevent race conditions where an order of operation upon the memory is expected. A contention or race condition often occurs when two or more threads need to perform operations on the same memory area, but the results of computations depends on the order in which these operations are performed. Mutexes are used for serializing shared resources such as memory. Anytime a global resource is accessed by more than one thread the resource should have a Mutex associated with it. Once can apply a mutex to protect a segment of memomry ("critical region") from other threads. Mutexes can be applied only to threads in a single process and do not work between processes as do semaphores.

Modified Program with Mutex:


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


/* Note scope of variable and mutex are the same */
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int gvar;
void* t1_fun(void *msg)
{
    int i;
    for(i = 0; i < 100; i++)
    {   
        pthread_mutex_lock( &mutex );
        gvar++;
        /*printf("[%d:%s] : gvar = %d.\n", pthread_self(), (char*) msg, gvar);*/
        printf("[%d:%s] : gvar = %d.\n", (long int)syscall(224), (char*) msg, gvar);
        pthread_mutex_unlock( &mutex );
    }   
}
...
...
Program Output:
$> gcc sample.c -lpthread
$> ls
a.out sample.c

$> ./a.out

[5164:thread1] : gvar = 99.
[5164:thread1] : gvar = 100.
...
...
[5165:thread2] : gvar = 199.
[5165:thread2] : gvar = 200.
Now, no matter how much ever you try executing this program, "gvar" at the end of the program will be always 200 (if you specify 100 as loop count in for loop).
NOTE THAT THE SCOPE OF THE VARIABLE AND THE MUTEX VARIABLE IS SAME. MEANS, GLOBAL VARIABLE PROTECTION SHOULD USE GLOBAL MUTEX VARIABLE!!

1 comment:

  1. Thank you Sir for sharing your knowledge. Learning a lot from your posts.
    Slight modification was required in this program instead of syscall(224) we should use syscall(SYS_gettid).

    ReplyDelete