If you need a thread to sleep it is usually because it has to wait for another thread to provide certain information or to prevent two threads from accessing the same critical area simultaneously ( which is the same thing).
One option to deal with these cases is to use a mutex . A mutex is a kind of traffic light. It only allows a thread to access the protected section (s) and block access to the rest:
#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<unistd.h>
// definicion del bloqueo (uno por region critica)
pthread_mutex_t lock;
int contador = 0;
void* NuevoTrabajo(void* arg)
{
for( int i=0; i<10; i++ )
{
pthread_mutex_lock(&lock);
int temp = contador + 1;
sleep(1);
contador = temp;
printf("Nuevo trabajo. Hay: %d\n",contador);
pthread_mutex_unlock(&lock);
sleep(1);
}
return NULL;
}
void* ConsumirTrabajo(void* arg)
{
sleep(2);
for( int i=0; i<10; i++ )
{
pthread_mutex_lock(&lock);
int temp = contador;
if( temp > 0 )
{
temp--;
sleep(1);
contador = temp;
printf("Trabajo consumido. Quedan: %d\n",contador);
}
else i--;
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main()
{
pthread_t t0, t1;
int err = pthread_create(&t0, NULL, &NuevoTrabajo, NULL);
if (err != 0)
{
printf("Error: %s", strerror(err));
return 0;
}
err = pthread_create(&t1, NULL, &ConsumirTrabajo, NULL);
if (err != 0)
{
printf("Error: %s", strerror(err));
return 0;
}
pthread_join(t0, NULL);
pthread_join(t1, NULL);
}
The code consists of a simple program with two threads: one that generates jobs and another that consumes them.
The program has several features:
- Use
sleep
to simulate workloads
- Makes a copy of the counter to allow dirty readings and writes (see below).
If you run the program you can get a sequence similar to this one:
Nuevo trabajo. Hay: 1
Nuevo trabajo. Hay: 2
Trabajo consumido. Quedan: 1
Trabajo consumido. Quedan: 0
Nuevo trabajo. Hay: 1
Trabajo consumido. Quedan: 0
Nuevo trabajo. Hay: 1
Trabajo consumido. Quedan: 0
...
With what you see some consistency ... the number of jobs only varies one by one.
What happens if we run the program without guards?
#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<unistd.h>
// definicion del bloqueo (uno por region critica)
pthread_mutex_t lock;
int contador = 0;
void* NuevoTrabajo(void* arg)
{
for( int i=0; i<10; i++ )
{
// pthread_mutex_lock(&lock);
int temp = contador + 1;
sleep(1);
contador = temp;
printf("Nuevo trabajo. Hay: %d\n",contador);
// pthread_mutex_unlock(&lock);
sleep(1);
}
return NULL;
}
void* ConsumirTrabajo(void* arg)
{
sleep(2);
for( int i=0; i<10; i++ )
{
// pthread_mutex_lock(&lock);
int temp = contador;
if( temp > 0 )
{
temp--;
sleep(1);
contador = temp;
printf("Trabajo consumido. Quedan: %d\n",contador);
}
else i--;
// pthread_mutex_unlock(&lock);
}
return NULL;
}
int main()
{
pthread_t t0, t1;
int err = pthread_create(&t0, NULL, &NuevoTrabajo, NULL);
if (err != 0)
{
printf("Error: %s", strerror(err));
return 0;
}
err = pthread_create(&t1, NULL, &ConsumirTrabajo, NULL);
if (err != 0)
{
printf("Error: %s", strerror(err));
return 0;
}
pthread_join(t0, NULL);
pthread_join(t1, NULL);
}
It happens that now the program becomes a little more chaotic:
Nuevo trabajo. Hay: 1
Trabajo consumido. Quedan: 0
Nuevo trabajo. Hay: 2
Trabajo consumido. Quedan: 1
Nuevo trabajo. Hay: 3
Trabajo consumido. Quedan: 0
Nuevo trabajo. Hay: 1
Trabajo consumido. Quedan: 0
...
Notice how there are now jumps in the number of jobs ... this is proof that the guards are working correctly. The guards protect the critical regions avoiding that the threads are stepped on the work.