hvn-network

My Blog List

Friday, August 26, 2016

Mutex

Created by Thomas

Mutexes: Locking and unlocking

A mutex (mutual exclusion) is the most basic form of synchronization.
A mutex is to protect a critical region, making sure that only one thread or only one process (if mutex is shared by processes) can access the critical region on given time.

The normal outline of code.
lock_the_mutex(..);
critical region
unlock_the_mutex(..);

We can allocate mutex statically.
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

or can allocate dynamically, then we must initialize it by pthread_mutex_init().

Following function to lock and unlock a mutex

int pthread_mutex_lock(pthread_mutex_t *mptr);
int pthread_mutex_unlock(pthread_mutex_t *mptr);


We use below example to show how mutex works.

In first program, two threads increases a common global variable without using mutex.

int glob = 0;   
                                   
static void * threadFunc_a(void *arg) {
    int loops = *((int *) arg);
    int loc, j;

    for (j = 0; j < loops; j++) {
        loc = glob;
        loc++;
        glob = loc;  
    }

    return NULL;
}

static void * threadFunc_b(void *arg) {
    int loops = *((int *) arg);
    int loc, j;

    for (j = 0; j < loops; j++) {
        loc = glob;
        loc++;
        glob = loc;  
    }

    return NULL;
}

int
main(int argc, char *argv[])
{
    pthread_t t1, t2;
    int loops, s;

    loops = argc;

    s = pthread_create(&t1, NULL, threadFunc_a, &loops);
    if (s != 0)
        perror(s, "pthread_create");
    s = pthread_create(&t2, NULL, threadFunc_b, &loops);
    if (s != 0)
        perror("pthread_create");

    s = pthread_join(t1, NULL);
    if (s != 0)
        perror("pthread_join");
    s = pthread_join(t2, NULL);
    if (s != 0)
        perror("pthread_join");

    printf("glob = %d\n", glob);
    exit(0);
}


For each thread, it increases the global variable 'loops' times.
First we copy the shared global variable to a local variable on it own stack, then increases the local variable, finally copy the local variable back to the shared global variable.

Output as below.






When 'loops' value is small (i.e 200) the result is expected. Remember that each thread increases independently the shared global value up to 'loops' times. Therefore, the final result should be double.

We note that when 'loops' value is large, the increase process is not correct. The reason is explained shortly below.






















Assume thread 1 copy glob variable (2000) to local variable. After that CPU time for it expires, leading thread 2 has chance to increase the glob variable. Suppose that thread 2 get time of CPU on it to increase the glob variable up to 3000. At this time, thread one obtains CPU again, it copies the value from local variable to global variable. Therefore, current value in glob variable changes from 3000 to 2000. That means that the increasing process of thread 2 is discarded.

To resolve this problem, we just make sure at any given time, only one thread can increase the glob variable. The code is modified as below.
int glob = 0;   
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

static void * threadFunc_a(void *arg) {
    int loops = *((int *) arg);
    int loc, j;

    for (j = 0; j < loops; j++) {
  pthread_mutex_lock(&mtx);
        loc = glob;
        loc++;
        glob = loc; 
  pthread_mutex_unlock(&mtx);
    }

    return NULL;
}

static void * threadFunc_b(void *arg) {
    int loops = *((int *) arg);
    int loc, j;

    for (j = 0; j < loops; j++) {
  pthread_mutex_lock(&mtx);
        loc = glob;
        loc++;
        glob = loc; 
  pthread_mutex_unlock(&mtx);
    }

    return NULL;
}

int
main(int argc, char *argv[])
{
    pthread_t t1, t2;
    int loops, s;
    if (argc != 2)
 {
  printf("usage: a.out <#loops>\n");
  exit(0);
 } 
    loops = atoi(argv[1]);

    s = pthread_create(&t1, NULL, threadFunc_a, &loops);
    if (s != 0)
        perror(s, "pthread_create");
    s = pthread_create(&t2, NULL, threadFunc_b, &loops);
    if (s != 0)
        perror("pthread_create");

    s = pthread_join(t1, NULL);
    if (s != 0)
        perror("pthread_join");
    s = pthread_join(t2, NULL);
    if (s != 0)
        perror("pthread_join");

    printf("glob = %d\n", glob);
    exit(0);
}


The correct result is following.





Resources
[1] The Linux programming interface, Michael Kerrisk
[2] Unix network programming Vol 2, W. Richard Stevens

1 comment:

  1. In the critical region, if changing code to {glob++} instead of {loc = glob;loc++;glob = loc}, what will happen?
    We can observe the behavior of accessing the critical region simultaneously, but it needs run more loops to check clearly behavior.
    Let's consider below action when read/write simultaneously value on register (to update glob's value).

    ReplyDelete