PtContact
PtContact

Reputation: 23

RESOLVED sleep methods hang the program threads in C pthreads (race condition, it had nothing to do with sleep)

I created a C program with pthreads, and it works perfectly until I add any sort of sleep function, whether it's be sleep() or usleep() or nanosleep().

Yes, before you ask, I know usleep() is deprecated and I have copied the correct usage of nanosleep() from another stackoferflow post, but even sleep() doesn't work.

The code (the critical section is in the "clients" thread, at line 99):

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <time.h>

#define LIBERA 0
#define OCCUPATA 1
#define TUTTE_SEDIE_OCCUPATE -1
#define OCCUPATA_DA_BARBIERE -2

#define NRCLIENTI 10
#define NRSEDIE 5

pthread_mutex_t poltrona_m, dorme_m, get_sedia_m, occupa_sedia_m;
sem_t sem;
int sedia_taglio = LIBERA, sedia_attesa[NRSEDIE];

void init_attr(void) {
    int i;
    for (i = 0; i < NRSEDIE; i++)
        sedia_attesa[i] = LIBERA;
    sem_init(&sem, 0, NRSEDIE);
    pthread_mutex_unlock(&poltrona_m);
    pthread_mutex_unlock(&dorme_m);
    pthread_mutex_unlock(&get_sedia_m);
    pthread_mutex_unlock(&occupa_sedia_m);
}

int my_sleep(long millis) {
    struct timespec req, rem;

    if (millis > 999) {
        req.tv_sec = (int) (millis / 1000);
        req.tv_nsec = (millis - ((long) req.tv_sec * 1000)) * 1000000;
    } else {
        req.tv_sec = 0;
        req.tv_nsec = millis * 1000000;
    }

    return nanosleep(&req, &rem);
}

int get_sedia_libera() {
    pthread_mutex_lock(&get_sedia_m);
    int i;
    for (i = 0; i < NRSEDIE; i++)
        if (sedia_attesa[i] == LIBERA)
            return i;
    pthread_mutex_unlock(&get_sedia_m);
    return TUTTE_SEDIE_OCCUPATE;
}

int occupa_sedia(int index) {
    pthread_mutex_lock(&occupa_sedia_m);
    int result;
    if(sedia_attesa[index] == LIBERA){
        sedia_attesa[index] = OCCUPATA;
        result = 1;
    }else
        result = 0;
    pthread_mutex_unlock(&occupa_sedia_m);
    return result;
}

void libera_sedia(int index) {
    sedia_attesa[index] = LIBERA;
}

void *barbiere(void *arg) {
    while (1)
    {
        if (sedia_taglio == OCCUPATA) {
            pthread_mutex_lock(&poltrona_m);
            sedia_taglio = LIBERA;
            pthread_mutex_unlock(&poltrona_m);
        }

        if (get_sedia_libera() == 0 && sedia_taglio == LIBERA) {
            sedia_taglio = OCCUPATA_DA_BARBIERE;
            pthread_mutex_lock(&dorme_m);
            printf("[BARBIERE] il barbiere dorme...\n");
        }
    }
}

void *cliente(void *arg) {
    int n;
    int *id = (void *)arg;

    while (1)
    {
        if (sedia_taglio == LIBERA || sedia_taglio == OCCUPATA_DA_BARBIERE) {

            pthread_mutex_lock(&poltrona_m);
            pthread_mutex_unlock(&dorme_m);
            sedia_taglio = OCCUPATA;
            printf("----[CLIENTE] Cliente \"%d\" si taglia i capelli!\n", *id);
            my_sleep(2000);
            sedia_taglio = LIBERA;
            pthread_mutex_unlock(&poltrona_m);
            pthread_exit(0);

        } else {
            n = get_sedia_libera();
            if(n == TUTTE_SEDIE_OCCUPATE) {
                fprintf(stderr, "----[CLIENTE] No posti liberi cliente \"%d\" lascia il negozio\n", *id);
                pthread_exit((void *)1);
            } else {
                sem_wait(&sem);
                n = get_sedia_libera();
                if(occupa_sedia(n) && n != TUTTE_SEDIE_OCCUPATE){
                    printf("----[CLIENTE] Cliente \"%d\" aspetta su sedia #%d\n", *id, n+1);
                    while (sedia_taglio != LIBERA);
                    sem_post(&sem);
                    libera_sedia(n);
                } else {
                    sem_post(&sem);
                    fprintf(stderr, "----[CLIENTE] No posti liberi cliente \"%d\" lascia il negozio\n", *id);
                    pthread_exit(0);
                }
            }
        }

    }
}

int main(int argc, char **argv) {

    init_attr();

    int i, id[NRCLIENTI];
    pthread_t barbiere_thread, cliente_thread[NRCLIENTI];

    if(pthread_create(&barbiere_thread, NULL, barbiere, NULL ) < 0){
        fprintf(stderr, "[MAIN] Error creating \"barber\" thread!\n");
        exit(EXIT_FAILURE);
    }
    printf("[MAIN] thread \"barber\" creato\n\n");

    for (i = 0; i < NRCLIENTI; ++i) {
        id[i] = i+1;
        printf("[MAIN] thread \"client %d\" creato\n", id[i]);
        if(pthread_create(&cliente_thread[i], NULL, cliente, &id[i]) < 0){
            fprintf(stderr, "[MAIN] Error creating \"client %d\" thread!\n", id[i]);
            exit(EXIT_FAILURE);
        }
    }

    for (i = 0; i < NRCLIENTI; i++){
        pthread_join(cliente_thread[i], NULL);
        printf("[MAIN]Joined client \"%d\"\n", i+1);
    }

    //il programma non finira mai se sta ad aspettare un thread con un ciclo while(1) al suo interno
    //senza condizioni di break!!!
    pthread_join(barbiere_thread, NULL);

    return 0;
}

Normal execution(this is how the program should work):

[MAIN] thread "client 1" creato
[BARBIERE] il barbiere dorme...
hey
[MAIN] thread "client 2" creato
----<<<<<<<<<<<<[CLIENTE] Cliente "1" si taglia i capelli!
[MAIN] thread "client 3" creato
----<<<<<<<<<<<<[CLIENTE] Cliente "2" si taglia i capelli!
[MAIN] thread "client 4" creato
----<<<<<<<<<<<<[CLIENTE] Cliente "3" si taglia i capelli!
[MAIN] thread "client 5" creato
----<<<<<<<<<<<<[CLIENTE] Cliente "4" si taglia i capelli!
[MAIN] thread "client 6" creato
----<<<<<<<<<<<<[CLIENTE] Cliente "5" si taglia i capelli!
[MAIN] thread "client 7" creato
----<<<<<<<<<<<<[CLIENTE] Cliente "6" si taglia i capelli!
[MAIN] thread "client 8" creato
----<<<<<<<<<<<<[CLIENTE] Cliente "7" si taglia i capelli!
[MAIN] thread "client 9" creato
----<<<<<<<<<<<<[CLIENTE] Cliente "8" si taglia i capelli!
[MAIN] thread "client 10" creato
----<<<<<<<<<<<<[CLIENTE] Cliente "9" si taglia i capelli!
----<<<<<<<<<<<<[CLIENTE] Cliente "10" si taglia i capelli!
[MAIN]Joined client "1"
[MAIN]Joined client "2"
[MAIN]Joined client "3"
[MAIN]Joined client "4"
[MAIN]Joined client "5"
[MAIN]Joined client "6"
[MAIN]Joined client "7"
[MAIN]Joined client "8"
[MAIN]Joined client "9"
[MAIN]Joined client "10"

Execution with any sleep method, sleep(1), nanosleep() with 1 second or 100k microsecond usleep() but I don't take usleep() into consideration because a) *its deprecated and * b) you cant put more or equal than 1million microseconds; usleep(1000000) is NOT valid

[MAIN] thread "client 1" creato
[BARBIERE] il barbiere dorme...
[MAIN] thread "client 2" creato
----[CLIENTE] Cliente "1" si taglia i capelli!
[MAIN] thread "client 3" creato
[MAIN] thread "client 4" creato
[MAIN] thread "client 5" creato
[MAIN] thread "client 6" creato
[MAIN] thread "client 7" creato
[MAIN] thread "client 8" creato
[MAIN] thread "client 9" creato
[MAIN] thread "client 10" creato
[MAIN]Joined client "1"

As you can see, the threads just... stall. I waited with the terminal open for 5 minutes, and still, nothing changed. The strangest thing is that if I run this in windows IntelliJ Clion using MinGW, the sleep() functions seem totally ignored (the program executes itself in a fraction of a second)!

Thanks for reading through all of this, I hope we can find a solution and I'd be grateful for any help I get!

Upvotes: 2

Views: 1201

Answers (1)

bruno
bruno

Reputation: 32596

You creates deadlock because you have race condition between threads.

Moving pthread_mutex_t poltrona_m, dorme_m, get_sedia_m, occupa_sedia_m; at global scope, adding these verbose functions:

const char * whoIam()
{
  if (pthread_self() == barbiere_thread)
    return "barbiere";

  for (int i = 0; i != NRCLIENTI; ++i) {
    if (pthread_self() == cliente_thread[i]) {
      static char str[NRCLIENTI][64];

      sprintf(str[i], "cliente %d", i+1); /* done several times but whatever */
      return str[i];
    }
  } 

  return "unknown-thread";
}

const char * whoIsMutex(pthread_mutex_t * m)
{
  if (m == &poltrona_m)
    return "poltrona";
  if (m == &dorme_m)
    return "dorme";
  if (m == &get_sedia_m)
    return "sedia";
  if (m == &occupa_sedia_m)
    return "occupa_sedia";
  return "unknown-mutex";
}

void my_pthread_mutex_unlock(pthread_mutex_t * m)
{
  printf("%s unlock %s\n", whoIam(), whoIsMutex(m));
  pthread_mutex_unlock(m);
}

void my_pthread_mutex_lock(pthread_mutex_t * m)
{
  printf("%s want to lock %s\n", whoIam(), whoIsMutex(m));
  pthread_mutex_lock(m);
  printf("%s locked %s\n", whoIam(), whoIsMutex(m));
}

and using my_pthread_mutex_lock and my_pthread_mutex_unlock out of init_attr the execution can be :

[MAIN] thread "barber" creato

[MAIN] thread "client 1" creato
barbiere want to lock sedia
barbiere locked sedia
barbiere want to lock dorme
barbiere locked dorme
cliente 1 want to lock poltrona
cliente 1 locked poltrona
cliente 1 unlock dorme           <<< does nothing because does not have it
----[CLIENTE] Cliente "1" si taglia i capelli!
[BARBIERE] il barbiere dorme...
barbiere want to lock poltrona   <<< wait while get by client 1
[MAIN] thread "client 2" creato
[MAIN] thread "client 3" creato
[MAIN] thread "client 4" creato
cliente 2 want to lock sedia
[MAIN] thread "client 5" creato
cliente 4 want to lock sedia
[MAIN] thread "client 6" creato
cliente 3 want to lock sedia
[MAIN] thread "client 7" creato
cliente 6 want to lock sedia
cliente 5 want to lock sedia
[MAIN] thread "client 8" creato
cliente 7 want to lock sedia
[MAIN] thread "client 9" creato
cliente 8 want to lock sedia
[MAIN] thread "client 10" creato
cliente 9 want to lock sedia
cliente 10 want to lock sedia
cliente 1 unlock poltrona     <<< finally unlock poltrona
barbiere locked poltrona      <<< wake up
barbiere unlock poltrona
barbiere want to lock sedia   <<< dead because get by itself
[MAIN]Joined client "1"

Adding my_sleep(1000); before sedia_taglio = occupata; in cliente helps to not have the race condition :

bruno@bruno-XPS-8300:/tmp$ gcc -Wall t.c -lpthread
bruno@bruno-XPS-8300:/tmp$ ./a.out
[MAIN] thread "barber" creato

[MAIN] thread "client 1" creato
[BARBIERE] il barbiere dorme...
[MAIN] thread "client 2" creato
[MAIN] thread "client 3" creato
[MAIN] thread "client 4" creato
[MAIN] thread "client 5" creato
[MAIN] thread "client 6" creato
[MAIN] thread "client 7" creato
[MAIN] thread "client 8" creato
[MAIN] thread "client 9" creato
[MAIN] thread "client 10" creato
----[CLIENTE] Cliente "1" si taglia i capelli!
[MAIN]Joined client "1"
----[CLIENTE] Cliente "3" si taglia i capelli!
----[CLIENTE] Cliente "2" si taglia i capelli!
[MAIN]Joined client "2"
[MAIN]Joined client "3"
----[CLIENTE] Cliente "4" si taglia i capelli!
[MAIN]Joined client "4"
----[CLIENTE] Cliente "5" si taglia i capelli!
[MAIN]Joined client "5"
----[CLIENTE] Cliente "6" si taglia i capelli!
[MAIN]Joined client "6"
----[CLIENTE] Cliente "7" si taglia i capelli!
[MAIN]Joined client "7"
----[CLIENTE] Cliente "8" si taglia i capelli!
[MAIN]Joined client "8"
----[CLIENTE] Cliente "9" si taglia i capelli!
[MAIN]Joined client "9"
----[CLIENTE] Cliente "10" si taglia i capelli!
[MAIN]Joined client "10"

Out of that, about the way to sleep, the function sleep blocks all the process, contrarily to usleep and nanosleep.


Here a proposal without race condition, nor making assumption on the mutex implementation (no undefined behavior).

There is only one mutex to protect datas consistency and for the conditions, I also use it to protect printing.

There are seats to wait, and one used to cut hairs. To manage clients in order of arrival they get a ticket having a number, when there is no free seat at all a client cannot get a ticket and will retry later. The number of the ticket of the client to have hairs cut is given by CurrentTicket (as a screen client read) and the signal NextClient is produced (as a beep) to allow clients to check if their turn comes. The number of the ticket in the distributor is managed by LastTicket. The signal HelloBarber is sent by a client when he reaches the barber and may be wake up him. The barber sleeps when there is no client.

I use a random time limited to 5 seconds for the delay used by a client to (re)try to get a place, and the barber cut hairs of a client in a random time limited to 3 seconds.

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

#define NCLIENT 10
#define NSEAT 5 /* seats to wait */

unsigned int Seed;
int Nseat = NSEAT  + 1; /* add 1 to have seat to cut hairs */
int CurrentTicket = 0;
int LastTicket = 0;

pthread_mutex_t Mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t NextClient = PTHREAD_COND_INITIALIZER;
pthread_cond_t HelloBarber = PTHREAD_COND_INITIALIZER;

/* return -1 is no seat, else ticket number */
int getTicket()
{
  int result;

  pthread_mutex_lock(&Mutex);
  if (Nseat == 0)
    result = -1;
  else {
    Nseat -= 1;
    result = ++LastTicket;
  }
  pthread_mutex_unlock(&Mutex);

  return result;
}

void randomSleep(int max)
{
  int millis = rand_r(&Seed) % (max * 1000);
  struct timespec req, rem;

  if (millis > 999) {
    req.tv_sec = (int) (millis / 1000);
    req.tv_nsec = (millis - ((long) req.tv_sec * 1000)) * 1000000;
  } else {
    req.tv_sec = 0;
    req.tv_nsec = millis * 1000000;
  }

  nanosleep(&req, &rem);
}

void * client(void * arg)
{
  int id = *((int *) arg);
  int ticket;
  int justArrived = 1;

  while (randomSleep(5), ((ticket = getTicket()) == -1)) {
    pthread_mutex_lock(&Mutex);
    printf("----[CLIENT] Client \"%d\" no place, will try again later\n", id);
    pthread_mutex_unlock(&Mutex);
  }

  pthread_mutex_lock(&Mutex);
  printf("----[CLIENT] Client \"%d\" got ticket number %d\n", id, ticket);

  while (ticket != CurrentTicket) {
    printf("----[CLIENT] Client \"%d\" not my turn\n", id);
    if (justArrived) {
      justArrived = 0;
      printf("----[CLIENT] Client \"%d\" seat to wait\n", id);
    }
    pthread_cond_wait(&NextClient, &Mutex);
  }
  printf("----[CLIENT] Client \"%d\" my turn\n", id);
  if (justArrived)
    printf("----[CLIENT] Client \"%d\" do not need to seat to wait\n", id);

  pthread_cond_signal(&HelloBarber);

  pthread_cond_wait(&NextClient, &Mutex);
  printf("----[CLIENT] Client \"%d\" done for me\n", id);
  pthread_mutex_unlock(&Mutex);

  return NULL;
}

void * barber(void * dummy)
{
  pthread_mutex_lock(&Mutex);

  puts("[BARBER] ready");
  CurrentTicket = 1;
  pthread_cond_signal(&NextClient);

  for (;;) {
    printf("[BARBER] screen indicates ticket %d\n", CurrentTicket);

    int sleep = (Nseat == NSEAT + 1);

    if (sleep)
      puts("[BARBER] no client, time to sleep");

    pthread_cond_wait(&HelloBarber, &Mutex); /* in all cases to be sure next client had time to see his turn */

    if (sleep)
      puts("[BARBER] woken up by a client");
    puts("[BARBER] cutting hairs");

    pthread_mutex_unlock(&Mutex);
    randomSleep(3); /* time to cut hairs of current client */
    pthread_mutex_lock(&Mutex);

    puts("[BARBER] haircut done");
    Nseat += 1;
    CurrentTicket += 1;
    pthread_cond_broadcast(&NextClient);
  }
}

int main()
{
  pthread_t barber_thread, client_thread[NCLIENT];
  int i, id[NCLIENT];

  Seed = time(NULL);

  if (pthread_create(&barber_thread, NULL, barber, NULL ) < 0){
    fprintf(stderr, "[MAIN] Error creating \"barber\" thread!\n");
    exit(EXIT_FAILURE);
  }

  pthread_mutex_lock(&Mutex);
  if (CurrentTicket == 0) {
    /* wait barber ready */
    pthread_cond_wait(&NextClient, &Mutex);
  }
  pthread_mutex_unlock(&Mutex);

  for (i = 0; i < NCLIENT; ++i) {
    id[i] = i+1;
    if (pthread_create(&client_thread[i], NULL, client, &id[i]) < 0) {
      fprintf(stderr, "[MAIN] Error creating \"client %d\" thread!\n", id[i]);
      exit(EXIT_FAILURE);
    }
  }

  for (i = 0; i < NCLIENT; i++) {
    pthread_join(client_thread[i], NULL);

    pthread_mutex_lock(&Mutex);
    printf("[MAIN] Joined client \"%d\"\n", i+1);
    pthread_mutex_unlock(&Mutex);
  }

  return 0;
}

Compilation and example of execution :

pi@raspberrypi:~ $ gcc -Wall barber.c  -lpthread
pi@raspberrypi:~ $ ./a.out
[BARBER] ready
[BARBER] screen indicates ticket 1
[BARBER] no client, time to sleep
----[CLIENT] Client "7" got ticket number 1
----[CLIENT] Client "7" my turn
----[CLIENT] Client "7" do not need to seat to wait
[BARBER] woken up by a client
[BARBER] cutting hairs
----[CLIENT] Client "6" got ticket number 2
----[CLIENT] Client "6" not my turn
----[CLIENT] Client "6" seat to wait
----[CLIENT] Client "5" got ticket number 3
----[CLIENT] Client "5" not my turn
----[CLIENT] Client "5" seat to wait
----[CLIENT] Client "1" got ticket number 4
----[CLIENT] Client "1" not my turn
----[CLIENT] Client "1" seat to wait
----[CLIENT] Client "3" got ticket number 5
----[CLIENT] Client "3" not my turn
----[CLIENT] Client "3" seat to wait
[BARBER] haircut done
[BARBER] screen indicates ticket 2
----[CLIENT] Client "7" done for me
----[CLIENT] Client "3" not my turn
----[CLIENT] Client "1" not my turn
----[CLIENT] Client "6" my turn
----[CLIENT] Client "5" not my turn
[BARBER] cutting hairs
----[CLIENT] Client "9" got ticket number 6
----[CLIENT] Client "9" not my turn
----[CLIENT] Client "9" seat to wait
----[CLIENT] Client "8" got ticket number 7
----[CLIENT] Client "8" not my turn
----[CLIENT] Client "8" seat to wait
----[CLIENT] Client "4" no place, will try again later
----[CLIENT] Client "2" no place, will try again later
----[CLIENT] Client "10" no place, will try again later
[BARBER] haircut done
[BARBER] screen indicates ticket 3
----[CLIENT] Client "8" not my turn
----[CLIENT] Client "5" my turn
----[CLIENT] Client "9" not my turn
----[CLIENT] Client "1" not my turn
----[CLIENT] Client "6" done for me
[BARBER] cutting hairs
----[CLIENT] Client "3" not my turn
[BARBER] haircut done
[BARBER] screen indicates ticket 4
----[CLIENT] Client "9" not my turn
----[CLIENT] Client "1" my turn
----[CLIENT] Client "3" not my turn
----[CLIENT] Client "5" done for me
----[CLIENT] Client "8" not my turn
[BARBER] cutting hairs
[BARBER] haircut done
[BARBER] screen indicates ticket 5
----[CLIENT] Client "8" not my turn
----[CLIENT] Client "1" done for me
[MAIN] Joined client "1"
----[CLIENT] Client "9" not my turn
----[CLIENT] Client "3" my turn
[BARBER] cutting hairs
----[CLIENT] Client "10" got ticket number 8
----[CLIENT] Client "10" not my turn
----[CLIENT] Client "10" seat to wait
----[CLIENT] Client "2" got ticket number 9
----[CLIENT] Client "2" not my turn
----[CLIENT] Client "2" seat to wait
----[CLIENT] Client "4" got ticket number 10
----[CLIENT] Client "4" not my turn
----[CLIENT] Client "4" seat to wait
[BARBER] haircut done
[BARBER] screen indicates ticket 6
----[CLIENT] Client "2" not my turn
----[CLIENT] Client "3" done for me
----[CLIENT] Client "4" not my turn
----[CLIENT] Client "8" not my turn
----[CLIENT] Client "9" my turn
----[CLIENT] Client "10" not my turn
[BARBER] cutting hairs
[BARBER] haircut done
[BARBER] screen indicates ticket 7
----[CLIENT] Client "2" not my turn
----[CLIENT] Client "9" done for me
----[CLIENT] Client "4" not my turn
----[CLIENT] Client "8" my turn
[BARBER] cutting hairs
----[CLIENT] Client "10" not my turn
[BARBER] haircut done
[BARBER] screen indicates ticket 8
----[CLIENT] Client "10" my turn
[BARBER] cutting hairs
----[CLIENT] Client "2" not my turn
----[CLIENT] Client "8" done for me
----[CLIENT] Client "4" not my turn
[BARBER] haircut done
[BARBER] screen indicates ticket 9
----[CLIENT] Client "10" done for me
----[CLIENT] Client "2" my turn
----[CLIENT] Client "4" not my turn
[BARBER] cutting hairs
[BARBER] haircut done
[BARBER] screen indicates ticket 10
----[CLIENT] Client "2" done for me
----[CLIENT] Client "4" my turn
[BARBER] cutting hairs
[MAIN] Joined client "2"
[MAIN] Joined client "3"
[BARBER] haircut done
[BARBER] screen indicates ticket 11
[BARBER] no client, time to sleep
----[CLIENT] Client "4" done for me
[MAIN] Joined client "4"
[MAIN] Joined client "5"
[MAIN] Joined client "6"
[MAIN] Joined client "7"
[MAIN] Joined client "8"
[MAIN] Joined client "9"
[MAIN] Joined client "10"
pi@raspberrypi:~ $ 

Upvotes: 2

Related Questions