Search code examples
cmultithreadingexitfree

How to gracefully protect/exit a multi-threaded program in C?


I have a C program where I run 3 branches of code: 2 that i started through pthread_create and the normal one.
I am wondering how to correctly protect it if my second thread fails to be created somehow.
Here is my code:


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

typedef struct s_philo{
    sem_t       *first;
    sem_t       *second;
    sem_t       *stop_A;
    sem_t       *stop_B;
    pthread_t   A_thread;
    pthread_t   B_thread;
}   t_philo;

void    sem_close_safe(sem_t    *sem)
{
    if (sem_close(sem) == -1)
        printf("Failed to close semaphore\n");
}

int    free_philo(t_philo *philo)
{
    if (philo->first)
        sem_close_safe(philo->first);
    if (philo->second)
        sem_close_safe(philo->second);
    if (philo->stop_A)
        sem_close_safe(philo->stop_A);
    if (philo->stop_B)
        sem_close_safe(philo->stop_B);
    free(philo);
    return (1);
}

void    *check_philo(t_philo *philo)
{
    void    *check;

    check = philo;
    if (!philo->first || !philo->second || !philo->stop_A || !philo->stop_B)
        check = NULL;
    return (check);
}

sem_t   *sem_open_new_safe(const char *name, unsigned int value)
{
    sem_t   *sem;

    sem = sem_open(name, O_CREAT | O_EXCL, 0644, value);
    if (errno == EEXIST)
    {
        if (sem_unlink(name) == -1)
            return (NULL);
        sem = sem_open(name, O_CREAT | O_EXCL, 0644, value);
    }
    if (sem == SEM_FAILED)
        return (NULL);
    if (sem_unlink(name) == -1)
    {
        sem_close_safe(sem);
        return (NULL);
    }
    return (sem);
}

void    *A(void *p)
{
    t_philo *philo;

    philo = (t_philo *) p;

    sem_wait(philo->stop_A);
    sem_post(philo->stop_A);
    return (NULL);
}

void    *B(void *p)
{
    t_philo *philo;

    philo = (t_philo *) p;

    sem_wait(philo->stop_B);
    sem_post(philo->stop_B);
    return (NULL);
}

int main(void)
{
    t_philo *philo;
    int i;

    philo = malloc(sizeof(*philo));
    philo->first = sem_open_new_safe("/first", 1);
    philo->second = sem_open_new_safe("/second", 1);
    philo->stop_A = sem_open_new_safe("/stop_A", 0);
    philo->stop_B = sem_open_new_safe("/stop_B", 0);
    if (!check_philo(philo))
        return (free_philo(philo));
    if (pthread_create(&philo->A_thread, NULL, &A, (void *)philo))
        return (free_philo(philo));
    if (pthread_create(&philo->B_thread, NULL, &B, (void *)philo))
        return (free_philo(philo));
    i = 0;
    while (i++ < 100)
    {
        if (sem_wait(philo->first) == -1)
            sem_post(philo->stop_B);
        if (sem_wait(philo->second) == -1)
            sem_post(philo->stop_A);
        printf("%d\n", i);
        sem_post(philo->second);
        sem_post(philo->first);
    }
    sem_post(philo->stop_B);
    sem_post(philo->stop_A);
    pthread_join(philo->A_thread, NULL);
    pthread_join(philo->B_thread, NULL);
    free_philo(philo);
    return (0);
}

Both of my A and B threads wait for semaphores on their first lines of code so they will never return on their own if I do not post these semaphores.

Should I pthread_join thread A ? Should I manually post some semaphores to force thread A to continue its execution and return ? Or maybe I should use pthread_detach ? I am a bit lost.

Edit: I have been asked to post more code to make it executable, but I have a lot of lines of code and it would just drown the above one. What I am looking for (if it exists) is not a guided code-specific answer, but more of a best practice to gracefully handle pthread_create errors.

Edit 2: I added the least code I could to make it runnable


Solution

  • The general case looks something like this pseudocode:

    if (!setup_A()) {
      exit(FAILURE);
    }
    if (!setup_B()) {
      teardown_A();
      exit(FAILURE);
    }
    if (!setup_C()) {
      teardown_B();
      teardown_A();
      exit(FAILURE);
    }
    
    do_successful_processing();
    
    teardown_C();
    teardown_B();
    teardown_A();
    

    and you're effectively asking how to write teardown_B().

    The general solution (assuming you can't just switch to C++ to use proper destructors and RAII) does not exist. The teardown is just as specific to the details of A, B, C and your application as the setup is.


    I am wondering how to correctly protect it if my second thread fails to be created somehow.

    The proximate answer is to tell the thread to quit (in some application-specific way), and then to join it.

    The actual semantics of requesting a shutdown are specific to your code, since you wrote the function that thread is executing. Here, it should be sufficient to sem_post the semaphone thread A is waiting on.

    NB. DO NOT use pthread_kill to shut down threads, if you can possibly avoid it. It's much better to write clean shutdown handling explicitly.