UC3M

Telematic/Audiovisual Syst./Communication Syst. Engineering

Systems Architecture

September 2017 - January 2018

11.3.  Mutex

As creating a concurrent program threads defined in the previous example, the programming model may prove insufficient. Some of the limitations of the pure use of a multithreading strategy include the following problems:

  • To return data, the thread must end.

  • There is no way to share data so that the threads can perform read or write operations safely.

To solve out this issue, the interface includes a new facility called pthread_mutex that allows the following operations:

  • Close a mutex with an atomic operation (lock) to lock the mutex. All threads attempting to lock the bolt from that moment fail and are blocked in the mutex.

  • Releasing the mutex with another atomic operation (unlock) that releases the mutex and allows another thread (blocked in the mutex) to it use.

The rest of the section covers these aspects in a practical way.

11.3.1.  Mutex: Motivation

Consider creating a program with two threads that increase the value of a global variable. That can be more easily done if we allow the two threads to read and write from the variable. The resulting code would be as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// compile with $ gcc -Wall -g *.c -pthread -o program
// run with ./program
// check with valgrind --tool=helgrind ./program
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

void* thread_run(void* data)
{ int i_data;
  sleep(2); 
  printf("[TH_ID:%ld]: Hello from the thread \n", pthread_self());
  printf("[TH_ID:%ld]: Reading %i \n", pthread_self(),(*(int*)data));
  i_data=(*(int*)data);
  sleep(1);
  i_data++;
  (*(int*)data)=i_data;
  printf("[TH_ID:%ld]: Writing %i \n", pthread_self(),(*(int*)data));
  printf("[TH_ID: %ld]: To exit...............\n",pthread_self());
  return data;
}

int main()
{ int i;
  pthread_t thread[2];
  int data=0;
  int thread_rc;
  
  for (i=0;i<2; i++)
  { printf("[MAIN:%ld]: Starting............ \n",pthread_self());
   if ((thread_rc=pthread_create(&thread[i],NULL,thread_run,&data))!=0)
    {
     printf("Error creating the thread. Code %i",thread_rc);
     return -1;
    }
  }
  
  sleep(1);
  printf("[MAIN:%ld]: Thread allocated \n",pthread_self());
  int *ptr_output_data;
  for ( i=0;i<2; i++)
  {
    pthread_join(thread[i],(void **)&ptr_output_data);
  }
  printf("[MAIN:%ld]: Thread returns %d \n",pthread_self(), *ptr_output_data);
  return 0;
} 

Which would make the following erroneous execution trace were generated:

[MAIN:2]: Starting............ 
[MAIN:2]: Starting............ 
[MAIN:2]: Thread allocated 
[TH_ID:0]: Hello from the thread 
[TH_ID:0]: Reading 0 
[TH_ID:6]: Hello from the thread 
[TH_ID:6]: Reading 0 
[TH_ID:6]: Writing 1 
[TH_ID:6]: To exit...............
[TH_ID:40]: Writing 1 
[TH_ID:0]: To exit...............
[MAIN:2]: Thread returns 1 

The problem with the code is that the read operation, modification and writing a result can not be performed concurrently. If two threads try to read and write at the same time the following "wrong" executions may happen (as they all return 1 instead of 2):

  • T_0 read(0), T_6 read(0), T_0 write(1), T_6 write(1)

  • T_6 read(0), T_0 read(0), T_0 write(1), T_6 write(1)

  • T_0 read(0), T_6 read(0), T_6 write(1), T_0 write(1)

  • T_6 read(0), T_0 read(0), T_6 write(1), T_0 write(1)

The program also has the following correct executions, that could be also obtained depending on how you run the infrastructure:

  • T_0 read(0), T_0 write(1), T_6 read(1), T_6 write(2)

  • T_0 read(0), T_0 write(1), T_6 read(1), T_6 write(2)

This misbehavior is called race condition.

A phtread_muxtex_unlock has the following behavior on the system:

  • If other threads are waiting for the mutex, it gives way to one of them (one of those blocked in the lock).