Проблема состояния гонки при синхронизации текстовых файлов с использованием потоков - PullRequest
1 голос
/ 07 февраля 2020

Ну, я начинаю изучение потоков POSIX в C, и я пытаюсь сделать каждый поток, прочитать строку файлов, ну и мне удалось заставить его работать, используя синхронизацию через переменная условия, но при чтении первой строки файла два потока могут читать одновременно, вызывая проблему условий гонки, почему это происходит? Я проверил это, используя инструмент helgrind от Valgrind.

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


pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

/*

  $cat file.txt
  test1
  test2
  test3
  test4
  test5
  test6
*/

FILE *fp;

void *func(void) {

      char c;

      fp = fopen("file.txt", "r");
      if(!fp) {
          perror("DEBUG: ");
          exit(EXIT_FAILURE);
      }

      pthread_mutex_lock(&mutex);
      while((c = fgetc(fp)) != EOF) {
         printf("TH = %zu CHARACTER = %c\n", pthread_self(), c);
         if(c == '\n') {
              pthread_cond_wait(&cond, &mutex);
            } else {
              pthread_cond_signal(&cond);
          }
      }
      pthread_mutex_unlock(&mutex);

      pthread_exit(NULL);
 }

 int main(void) {

    pthread_t thread1, thread2;

    pthread_create(&thread1, NULL, (void *)func, NULL);
    sleep(1);
    pthread_create(&thread2, NULL, (void *)func, NULL);

    pthread_join(thread2, NULL);

    fclose(fp);

    return 0;
}  

Ответы [ 2 ]

0 голосов
/ 07 февраля 2020

Оба потока открывают файл и присваивают ему fp.

После того, как первый поток открывает файл и читает первую запись, следующий поток снова открывает файл и читает первую запись.

Исправьте это, открыв файл в функции main и передав FILE * в качестве параметра функции потока. Это также исправляет функцию потока, давая ей подпись, которую ожидает pthread_create, а именно void *(*)(void *).

void *func(void *param) {

      char c;

      FILE *fp = param;
      pthread_mutex_lock(&mutex);
      while((c = fgetc(fp)) != EOF) {
         printf("TH = %zu CHARACTER = %c\n", pthread_self(), c);
         if(c == '\n') {
              pthread_cond_wait(&cond, &mutex);
            } else {
              pthread_cond_signal(&cond);
          }
      }
      pthread_mutex_unlock(&mutex);

      pthread_exit(NULL);
 }

 int main(void) {

    pthread_t thread1, thread2;

      FILE *fp = fopen("file.txt", "r");
      if(!fp) {
          perror("DEBUG: ");
          exit(EXIT_FAILURE);
      }

    pthread_create(&thread1, NULL, func, fp);
    sleep(1);
    pthread_create(&thread2, NULL, func, fp);

    pthread_join(thread2, NULL);

    fclose(fp);

    return 0;
}  
0 голосов
/ 07 февраля 2020

Когда ваши темы вводят func, они оба вызывают fp = fopen("file.txt", "r"). Это означает, что теоретически вы могли бы на любом этапе чтения файла, когда FILE*, на который указывает fp, извлекается из-под потока, который был запущен первым.

Если каждый поток должен прочитать весь файл, тогда вы должны потянуть ваше объявление FILE *fp туда, где вы открываете файл, чтобы он был локальной переменной. Если вы воспользуетесь этим подходом, убедитесь, что вы закрыли оба FILE* s.

Если они должны совместно использовать указатель, как он настроен в данный момент, вам нужно безопасно открыть файл. Есть два способа сделать это sh в зависимости от ваших потребностей.

1) Вам следует изменить объявление, чтобы также установить значение NULL. Затем внутри func переместите pthread_mutex_lock(&mutex) вверх над вызовом на fopen(). Тогда и только звоните fopen(), если fp == NULL.

2) Вы также можете переместить ваш вызов на fopen() на main() до того, как начнете pthreads. Это делает так, что только один поток должен беспокоиться о том, чтобы открыть файл, и он сбалансирован с вашим вызовом fclose(fp), поэтому вы знаете, что fp является действительным указателем.

Также как сторона обратите внимание, что, как правило, рекомендуется присоединиться ко всем темам. Если вы используете Valgrind, он будет жаловаться на утечку потока 1, так как он не присоединен. Для такого простого случая, как этот, и с тем, как ведут себя ваши потоки, в этом случае это не должно иметь значения, но это хорошая привычка просто получить из get go, если вы изучаете pthreads.

...