Как предотвратить состояние гонки, когда несколько потоков пишут в один и тот же файловый дескриптор на C?

У меня есть следующая функция, которая будет работать в потоке:

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);
}

каждая из функций печати будет иметь следующий формат:

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");
}

Это создает состояние гонки, приводящее к записи неправильных значений в терминал. Если я заменю ft_print (который является собственной реализацией, которая, как предполагается, работает так же, как printf) на исходный printf, он работает нормально. Почему? printf использует мьютекс перед печатью? как я могу исправить свой код?

Обновлено:

Ссылка на реализацию ft_printf, она слишком велика, чтобы помещать ее сюда.

Нам нужно увидеть ft_print, чтобы сказать, почему его поведение отличается от printf.

Schwern 07.03.2024 22:40

Я добавил ссылку с реализацией

Vinicius Bass 07.03.2024 22:47

Ответ на ваш вопрос — да, printf() должен быть потокобезопасным, поэтому он должен использовать мьютекс. Прочтите справочную страницу.

Barmar 07.03.2024 23:00
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
103
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Если я заменю ft_print на исходный printf, все будет работать нормально. Почему?

POSIX требует, чтобы printf был потокобезопасным.

Но последовательность операторов printf не атомарна, так что вам тоже повезло.

Вам необходимо убедиться, что группа вызовов printf выполняется атомарно. В этом случае лучшее решение — объединить их в один.

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

как я могу исправить свой код?

Как и в случае с printf, вам необходимо сделать группу вызовов атомарной. Да, для этого вы можете использовать мьютекс.

// 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.

Обратите внимание, что простое помещение мьютекса вслепую в ft_printf не поможет — вам нужно определить, что нужно вывести атомарно, и убедиться, что несколько таких мьютексов не смешиваются.

Chris Dodd 08.03.2024 01:33

@ Крис Додд, верно. Это должно быть вокруг группы вызовов. Это необходимо и для printf, как уже сказано в ответе. (Ну, это подразумевалось, но я просто сделал это более явным.)

ikegami 08.03.2024 01:35

Реальная разница, которую вы видите в printf, заключается в БУФЕРИЗАЦИИ, а не в синхронизации.

Printf (и функции stdio в целом) на самом деле просто записывают вывод в буфер и фактически ничего не выводят, пока буфер не будет очищен. По умолчанию это происходит всякий раз, когда выводится символ \n (новая строка) или когда буфер заполняется. Итак, с вашим кодом, если у вас было:

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

первая строка будет просто записываться в буфер, а вторая строка будет добавлена ​​в буфер, а затем вся строка будет записана как один вызов. При использовании отдельных процессов это дает эффект атомарности, поскольку базовый вызов записи запишет строку на терминал как атомарную единицу.

Обратите внимание, что простое использование внутреннего мьютекса и выполнение каждого вызова ft_printfatomic не поможет, поскольку эти два вызова действительно необходимо объединить в атомарный блок. Использование мьютекса в каждом print_color для атомарности всего вызова будет работать.

Похоже, что ваш существующий код использует pthreads, поэтому простое использование printf (с общим буфером STDOUT и блокировкой мьютекса в каждом вызове printf) может показаться работающим, но все равно будут наблюдаться случайные сбои, если два потока попытаются записать строки разных цветов. одновременно.

Запись байтов в буфер по своей сути не является атомарной; printf() блокирует мьютекс для обеспечения атомарности.

Jeremy Friesner 07.03.2024 23:39

@JeremyFriesner: да, но в данном случае это оказывается неадекватным (или очень актуальным).

Chris Dodd 07.03.2024 23:41

Re «Это дает эффект атомарности». Нет, это не так. Буферизация вообще не имеет значения.

ikegami 08.03.2024 03:57

Другие вопросы по теме