Home > Software design >  How to gracefully protect/exit a multi-threaded program in C?
How to gracefully protect/exit a multi-threaded program in C?

Time:01-28

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

CodePudding user response:

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.

CodePudding user response:

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

Easiest would be to simply call exit(1) in the case that thread creation fails. That will terminate the whole process, including all its threads. All resources owned by the process will be cleaned up by the system.

That does create a possible issue if the program wants to clean up any persistent resources, such as files or named semaphores. Often that's not a major issue, for if the program fails then it may be ok for its termination to be less tidy than if it succeeds. Nevertheless, there are ways to reduce that impact.

In particular, you can minimize the program's use of modifiable persistent resources. For example, your program would be all-around cleaner if it used unnamed semaphores instead of named ones. You would not need to watch out for existing ones when you create them, and since they are being used only within a single process, you would not need to worry about failing to clean them up before terminating.

But where you want some kind of affirmative cleanup to happen when the program terminates, you always have the option to use atexit() to register an exit handler to perform that. There are some caveats, but it's good to be aware of this option.

Should I pthread_join thread A ?

In your particular example, I see no reason to do so. It might be more appropriate in other cases.

Should I manually post some semaphores to force thread A to continue its execution and return ?

If you plan to join thread A then you need to ensure that it will, in fact, terminate. In this case, it looks like yes, you could achieve that by semaphore manipulation, but cases where it actually mattered would be more complex. Ensuring A's timely would probably not be so simple in such cases.

Or maybe I should use pthread_detach ?

There is no advantage at all to doing that in this case. Thread A will be terminated when the process terminates, regardless of whether it is detached. And presumably that's what you want in your example, for it would not make progress if it were the only live thread left in the process.

  • Related