How to prevent race condition when multiple threads are writing in the same file descriptor in C?

78 views Asked by At

I have the following function that will run in the thread:

void    *dinning_handler(void *arg)
{
    t_philo         *philo;
    struct timeval  start;

    philo = (t_philo *)arg;
    gettimeofday(&start, NULL);
    philo->last_meal_time = start;
    while (philo->max_eats == -1 || philo->eats < philo->max_eats)
    {
        print_blue("is thinking", philo->id, get_ts_in_ms());
        pthread_mutex_lock(philo->left_fork);
        pthread_mutex_lock(philo->right_fork);
        print_blue("has taken a fork", philo->id, get_ts_in_ms());
        print_green("is eating", philo->id, get_ts_in_ms());
        usleep(philo->time_to_eat * 1000);
        philo->eats++;
        gettimeofday(&philo->last_meal_time, NULL);
        pthread_mutex_unlock(philo->left_fork);
        pthread_mutex_unlock(philo->right_fork);
        print_blue("is sleeping", philo->id, get_ts_in_ms());
        usleep(philo->time_to_sleep * 1000);
    }
    return (NULL);
}

each of the print functions will have the following format:

void    print_red(char **msg, int id, long time)
{
    ft_printf("\033[1;31m");
    ft_printf("%u %d %s\n", time, id, msg);
    ft_printf("\033[0m");
}

This generate a race condition leading to write wrong values in the terminal. If I replace ft_print(which is a self implementation that suppose to work the same way of printf) for the original printf it works fine. Why? do printf use mutex before print? how can I fix my code?

EDIT:

Link for ft_printf implementation, it is too big to put it here

2

There are 2 answers

2
ikegami On BEST ANSWER

If I replace ft_print for the original printf it works fine. Why?

POSIX requires printf to be thread-safe.

But a sequence of printf statements is not atomic, so you also got lucky.

You need to ensure the group of printf calls are executed atomically. In this case, the best solution is to combine them into one.

printf(
   "\033[1;31m"
   "%u %d %s\n"
   "\033[0m",
   time, id, msg
);

how can I fix my code?

As with printf, you need to make the group of calls to ft_printf atomic. Yes, you could use a mutex to achieve this.

// Lock the mutex here.
ft_printf( "\033[1;31m" );
ft_printf( "%u %d %s\n", time, id, msg );
ft_printf( "\033[0m" );
// Unlock it here.
3
Chris Dodd On

The real difference you're seeing from printf is coming from BUFFERING, not synchronization.

Printf (and stdio functions in general) all actually just write output into a buffer, and don't actually output anything until the buffer is flushed. By default, that happens whenever a \n (newline) character is output, or when the buffer fills up. So with your code, if you had:

printf("\033[1;31m");
printf("%u %d %s\n", time, id, msg);

the first line would just write to the buffer, and the second line would then be appended to the buffer, and then the whole line will be written as one call. When using separate processes, this gives the effect of having things be atomic, as the underlying write call will write the line to the terminal as an atomic unit.

Note that just using a internal mutex and making each call to ft_printf atomic wouldn't help, as these two calls really need to be combined into an atomic unit. Using a mutex in each print_color to make the whole call atomic would work.

Your existing code appears to use pthreads, so just using printf (with the shared STDOUT buffer, and a mutex lock in each printf call) might appear to work, but it will still see occasional failures if two threads try to write lines with different colors simultaneously.